1 /***************************************************************************
2     qgsappbrowserproviders.cpp
3     ---------------------------
4     begin                : September 2017
5     copyright            : (C) 2017 by 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 "qgisapp.h"
17 #include "qgsapplication.h"
18 #include "qgsappbrowserproviders.h"
19 #include "qgsbookmarkeditordialog.h"
20 #include "qgsmapcanvas.h"
21 #include "qgsmessagebar.h"
22 #include "qgsproject.h"
23 #include "qgsstyleexportimportdialog.h"
24 #include "qgsstyle.h"
25 #include "qgslayertreenode.h"
26 #include "qgslayertree.h"
27 #include "qgsstylemanagerdialog.h"
28 #include "qgsguiutils.h"
29 #include "qgsfileutils.h"
30 
31 #include <QDesktopServices>
32 #include <QMessageBox>
33 #include <QFileDialog>
34 #include <QUrl>
35 
iconBookmarks()36 QIcon QgsBookmarksItem::iconBookmarks()
37 {
38   return QgsApplication::getThemeIcon( QStringLiteral( "/mActionShowBookmarks.svg" ) );
39 }
40 
sortKey() const41 QVariant QgsBookmarksItem::sortKey() const
42 {
43   return QStringLiteral( " 1" );
44 }
45 
iconBookmarkManager()46 QIcon QgsBookmarkManagerItem::iconBookmarkManager()
47 {
48   return QgsApplication::getThemeIcon( QStringLiteral( "/mIconFolder.svg" ) );
49 }
50 
iconBookmarkGroup()51 QIcon QgsBookmarkGroupItem::iconBookmarkGroup()
52 {
53   return QgsApplication::getThemeIcon( QStringLiteral( "/mIconFolder.svg" ) );
54 }
55 
iconBookmark()56 QIcon QgsBookmarkItem::iconBookmark()
57 {
58   return QgsApplication::getThemeIcon( QStringLiteral( "/mItemBookmark.svg" ) );
59 }
60 
hasDragEnabled() const61 bool QgsBookmarkItem::hasDragEnabled() const
62 {
63   return true;
64 }
65 
mimeUri() const66 QgsMimeDataUtils::Uri QgsBookmarkItem::mimeUri() const
67 {
68   QgsMimeDataUtils::Uri u;
69   u.layerType = QStringLiteral( "custom" );
70   u.providerKey = QStringLiteral( "bookmark" );
71   u.name = name();
72 
73   QDomDocument doc;
74   doc.appendChild( mBookmark.writeXml( doc ) );
75   u.uri = doc.toString();
76 
77   return u;
78 }
79 
80 //
81 // QgsQlrDataItem
82 //
83 
QgsQlrDataItem(QgsDataItem * parent,const QString & name,const QString & path)84 QgsQlrDataItem::QgsQlrDataItem( QgsDataItem *parent, const QString &name, const QString &path )
85   : QgsLayerItem( parent, name, path, path, Qgis::BrowserLayerType::NoType, QStringLiteral( "qlr" ) )
86 {
87   setState( Qgis::BrowserItemState::Populated ); // no children
88   setIconName( QStringLiteral( ":/images/icons/qgis-icon-16x16.png" ) );
89   setToolTip( QDir::toNativeSeparators( path ) );
90   mCapabilities |= Qgis::BrowserItemCapability::ItemRepresentsFile;
91 }
92 
hasDragEnabled() const93 bool QgsQlrDataItem::hasDragEnabled() const
94 {
95   return true;
96 }
97 
mimeUri() const98 QgsMimeDataUtils::Uri QgsQlrDataItem::mimeUri() const
99 {
100   QgsMimeDataUtils::Uri u;
101   u.layerType = QStringLiteral( "custom" );
102   u.providerKey = QStringLiteral( "qlr" );
103   u.name = name();
104   u.uri = path();
105   u.filePath = path();
106   return u;
107 }
108 
mimeUris() const109 QgsMimeDataUtils::UriList QgsQlrDataItem::mimeUris() const
110 {
111   return QgsMimeDataUtils::UriList() << QgsQlrDataItem::mimeUri();
112 }
113 
114 //
115 // QgsQlrDataItemProvider
116 //
117 
name()118 QString QgsQlrDataItemProvider::name()
119 {
120   return QStringLiteral( "QLR" );
121 }
122 
capabilities() const123 int QgsQlrDataItemProvider::capabilities() const
124 {
125   return QgsDataProvider::File;
126 }
127 
createDataItem(const QString & path,QgsDataItem * parentItem)128 QgsDataItem *QgsQlrDataItemProvider::createDataItem( const QString &path, QgsDataItem *parentItem )
129 {
130   const QFileInfo fileInfo( path );
131 
132   if ( fileInfo.suffix().compare( QLatin1String( "qlr" ), Qt::CaseInsensitive ) == 0 )
133   {
134     return new QgsQlrDataItem( parentItem, fileInfo.fileName(), path );
135   }
136   return nullptr;
137 }
138 
customUriProviderKey() const139 QString QgsQlrDropHandler::customUriProviderKey() const
140 {
141   return QStringLiteral( "qlr" );
142 }
143 
handleCustomUriDrop(const QgsMimeDataUtils::Uri & uri) const144 void QgsQlrDropHandler::handleCustomUriDrop( const QgsMimeDataUtils::Uri &uri ) const
145 {
146   const QString path = uri.uri;
147   QgisApp::instance()->openLayerDefinition( path );
148 }
149 
150 //
151 // QgsQptDataItemProvider
152 //
153 
name()154 QString QgsQptDataItemProvider::name()
155 {
156   return QStringLiteral( "QPT" );
157 }
158 
capabilities() const159 int QgsQptDataItemProvider::capabilities() const
160 {
161   return QgsDataProvider::File;
162 }
163 
createDataItem(const QString & path,QgsDataItem * parentItem)164 QgsDataItem *QgsQptDataItemProvider::createDataItem( const QString &path, QgsDataItem *parentItem )
165 {
166   const QFileInfo fileInfo( path );
167 
168   if ( fileInfo.suffix().compare( QLatin1String( "qpt" ), Qt::CaseInsensitive ) == 0 )
169   {
170     return new QgsQptDataItem( parentItem, fileInfo.baseName(), path );
171   }
172   return nullptr;
173 }
174 
175 //
176 // QgsQptDropHandler
177 //
178 
customUriProviderKey() const179 QString QgsQptDropHandler::customUriProviderKey() const
180 {
181   return QStringLiteral( "qpt" );
182 }
183 
handleCustomUriDrop(const QgsMimeDataUtils::Uri & uri) const184 void QgsQptDropHandler::handleCustomUriDrop( const QgsMimeDataUtils::Uri &uri ) const
185 {
186   const QString path = uri.uri;
187   QgisApp::instance()->openTemplate( path );
188 }
189 
handleFileDrop(const QString & file)190 bool QgsQptDropHandler::handleFileDrop( const QString &file )
191 {
192   const QFileInfo fi( file );
193   if ( fi.completeSuffix().compare( QLatin1String( "qpt" ), Qt::CaseInsensitive ) == 0 )
194   {
195     QgisApp::instance()->openTemplate( file );
196     return true;
197   }
198   return false;
199 }
200 
201 //
202 // QgsQptDataItem
203 //
204 
QgsQptDataItem(QgsDataItem * parent,const QString & name,const QString & path)205 QgsQptDataItem::QgsQptDataItem( QgsDataItem *parent, const QString &name, const QString &path )
206   : QgsDataItem( Qgis::BrowserItemType::Custom, parent, name, path )
207 {
208   setState( Qgis::BrowserItemState::Populated ); // no children
209   setIconName( QStringLiteral( "/mIconQptFile.svg" ) );
210   setToolTip( QDir::toNativeSeparators( path ) );
211   mCapabilities |= Qgis::BrowserItemCapability::ItemRepresentsFile;
212 }
213 
hasDragEnabled() const214 bool QgsQptDataItem::hasDragEnabled() const
215 {
216   return true;
217 }
218 
mimeUri() const219 QgsMimeDataUtils::Uri QgsQptDataItem::mimeUri() const
220 {
221   QgsMimeDataUtils::Uri u;
222   u.layerType = QStringLiteral( "custom" );
223   u.providerKey = QStringLiteral( "qpt" );
224   u.name = name();
225   u.uri = path();
226   u.filePath = path();
227   return u;
228 }
229 
handleDoubleClick()230 bool QgsQptDataItem::handleDoubleClick()
231 {
232   QgisApp::instance()->openTemplate( path() );
233   return true;
234 }
235 
actions(QWidget * parent)236 QList<QAction *> QgsQptDataItem::actions( QWidget *parent )
237 {
238   QAction *newLayout = new QAction( tr( "New Layout from Template" ), parent );
239   connect( newLayout, &QAction::triggered, this, [ = ]
240   {
241     QgisApp::instance()->openTemplate( path() );
242   } );
243   return QList<QAction *>() << newLayout;
244 }
245 
246 //
247 // QgsPyDataItem
248 //
249 
QgsPyDataItem(QgsDataItem * parent,const QString & name,const QString & path)250 QgsPyDataItem::QgsPyDataItem( QgsDataItem *parent, const QString &name, const QString &path )
251   : QgsDataItem( Qgis::BrowserItemType::Custom, parent, name, path )
252 {
253   setState( Qgis::BrowserItemState::Populated ); // no children
254   setIconName( QStringLiteral( "/mIconPythonFile.svg" ) );
255   setToolTip( QDir::toNativeSeparators( path ) );
256   mCapabilities |= Qgis::BrowserItemCapability::ItemRepresentsFile;
257 }
258 
hasDragEnabled() const259 bool QgsPyDataItem::hasDragEnabled() const
260 {
261   return true;
262 }
263 
mimeUri() const264 QgsMimeDataUtils::Uri QgsPyDataItem::mimeUri() const
265 {
266   QgsMimeDataUtils::Uri u;
267   u.layerType = QStringLiteral( "custom" );
268   u.providerKey = QStringLiteral( "py" );
269   u.name = name();
270   u.uri = path();
271   u.filePath = path();
272   return u;
273 }
274 
handleDoubleClick()275 bool QgsPyDataItem::handleDoubleClick()
276 {
277   QgisApp::instance()->runScript( path() );
278   return true;
279 }
280 
actions(QWidget * parent)281 QList<QAction *> QgsPyDataItem::actions( QWidget *parent )
282 {
283   QAction *runScript = new QAction( tr( "&Run Script" ), parent );
284   connect( runScript, &QAction::triggered, this, [ = ]
285   {
286     QgisApp::instance()->runScript( path() );
287   } );
288   QAction *editScript = new QAction( tr( "Open in External &Editor" ), this );
289   connect( editScript, &QAction::triggered, this, [ = ]
290   {
291     QDesktopServices::openUrl( QUrl::fromLocalFile( path() ) );
292   } );
293   return QList<QAction *>() << runScript << editScript;
294 }
295 
296 //
297 // QgsPyDataItemProvider
298 //
299 
name()300 QString QgsPyDataItemProvider::name()
301 {
302   return QStringLiteral( "py" );
303 }
304 
capabilities() const305 int QgsPyDataItemProvider::capabilities() const
306 {
307   return QgsDataProvider::File;
308 }
309 
createDataItem(const QString & path,QgsDataItem * parentItem)310 QgsDataItem *QgsPyDataItemProvider::createDataItem( const QString &path, QgsDataItem *parentItem )
311 {
312   const QFileInfo fileInfo( path );
313 
314   if ( fileInfo.suffix().compare( QLatin1String( "py" ), Qt::CaseInsensitive ) == 0 )
315   {
316     return new QgsPyDataItem( parentItem, fileInfo.baseName(), path );
317   }
318   return nullptr;
319 }
320 
321 //
322 // QgsPyDropHandler
323 //
324 
customUriProviderKey() const325 QString QgsPyDropHandler::customUriProviderKey() const
326 {
327   return QStringLiteral( "py" );
328 }
329 
handleCustomUriDrop(const QgsMimeDataUtils::Uri & uri) const330 void QgsPyDropHandler::handleCustomUriDrop( const QgsMimeDataUtils::Uri &uri ) const
331 {
332   const QString path = uri.uri;
333   QgisApp::instance()->runScript( path );
334 }
335 
handleFileDrop(const QString & file)336 bool QgsPyDropHandler::handleFileDrop( const QString &file )
337 {
338   const QFileInfo fi( file );
339   if ( fi.completeSuffix().compare( QLatin1String( "py" ), Qt::CaseInsensitive ) == 0 )
340   {
341     QgisApp::instance()->runScript( file );
342     return true;
343   }
344   return false;
345 }
346 
347 
348 
349 
350 //
351 // QgsStyleXmlDataItem
352 //
353 
QgsStyleXmlDataItem(QgsDataItem * parent,const QString & name,const QString & path)354 QgsStyleXmlDataItem::QgsStyleXmlDataItem( QgsDataItem *parent, const QString &name, const QString &path )
355   : QgsDataItem( Qgis::BrowserItemType::Custom, parent, name, path )
356 {
357   setState( Qgis::BrowserItemState::Populated ); // no children
358   setIconName( QStringLiteral( "/mActionStyleManager.svg" ) );
359   setToolTip( QStringLiteral( "<b>%1</b><br>%2" ).arg( tr( "QGIS style library" ), QDir::toNativeSeparators( path ) ) );
360   mCapabilities |= Qgis::BrowserItemCapability::ItemRepresentsFile;
361 }
362 
hasDragEnabled() const363 bool QgsStyleXmlDataItem::hasDragEnabled() const
364 {
365   return true;
366 }
367 
mimeUri() const368 QgsMimeDataUtils::Uri QgsStyleXmlDataItem::mimeUri() const
369 {
370   QgsMimeDataUtils::Uri u;
371   u.layerType = QStringLiteral( "custom" );
372   u.providerKey = QStringLiteral( "style_xml" );
373   u.name = name();
374   u.uri = path();
375   u.filePath = path();
376   return u;
377 }
378 
handleDoubleClick()379 bool QgsStyleXmlDataItem::handleDoubleClick()
380 {
381   browseStyle( mPath );
382   return true;
383 }
384 
actions(QWidget * parent)385 QList<QAction *> QgsStyleXmlDataItem::actions( QWidget *parent )
386 {
387   QAction *browseAction = new QAction( tr( "&Open Style…" ), parent );
388   const QString path = mPath;
389   connect( browseAction, &QAction::triggered, this, [path]
390   {
391     browseStyle( path );
392   } );
393 
394   QAction *importAction = new QAction( tr( "&Import Style…" ), parent );
395   connect( importAction, &QAction::triggered, this, [path]
396   {
397     QgsStyleExportImportDialog dlg( QgsStyle::defaultStyle(), QgisApp::instance(), QgsStyleExportImportDialog::Import );
398     dlg.setImportFilePath( path );
399     dlg.exec();
400   } );
401   return QList<QAction *>() << browseAction << importAction;
402 }
403 
browseStyle(const QString & xmlPath)404 void QgsStyleXmlDataItem::browseStyle( const QString &xmlPath )
405 {
406   QgsStyle s;
407   s.createMemoryDatabase();
408 
409   auto cursorOverride = std::make_unique< QgsTemporaryCursorOverride >( Qt::WaitCursor );
410   if ( s.importXml( xmlPath ) )
411   {
412     cursorOverride.reset();
413     const QFileInfo fi( xmlPath );
414     QgsStyleManagerDialog dlg( &s, QgisApp::instance(), Qt::WindowFlags(), true );
415     dlg.setSmartGroupsVisible( false );
416     dlg.setFavoritesGroupVisible( false );
417     dlg.setBaseStyleName( fi.baseName() );
418     dlg.exec();
419   }
420 }
421 
422 //
423 // QgsStyleXmlDataItemProvider
424 //
425 
name()426 QString QgsStyleXmlDataItemProvider::name()
427 {
428   return QStringLiteral( "style_xml" );
429 }
430 
capabilities() const431 int QgsStyleXmlDataItemProvider::capabilities() const
432 {
433   return QgsDataProvider::File;
434 }
435 
createDataItem(const QString & path,QgsDataItem * parentItem)436 QgsDataItem *QgsStyleXmlDataItemProvider::createDataItem( const QString &path, QgsDataItem *parentItem )
437 {
438   if ( QgsStyle::isXmlStyleFile( path ) )
439   {
440     return new QgsStyleXmlDataItem( parentItem, QFileInfo( path ).fileName(), path );
441   }
442   return nullptr;
443 }
444 
445 //
446 // QgsStyleXmlDropHandler
447 //
448 
customUriProviderKey() const449 QString QgsStyleXmlDropHandler::customUriProviderKey() const
450 {
451   return QStringLiteral( "style_xml" );
452 }
453 
handleCustomUriDrop(const QgsMimeDataUtils::Uri & uri) const454 void QgsStyleXmlDropHandler::handleCustomUriDrop( const QgsMimeDataUtils::Uri &uri ) const
455 {
456   QgsStyleExportImportDialog dlg( QgsStyle::defaultStyle(), QgisApp::instance(), QgsStyleExportImportDialog::Import );
457   dlg.setImportFilePath( uri.uri );
458   dlg.exec();
459 }
460 
handleFileDrop(const QString & file)461 bool QgsStyleXmlDropHandler::handleFileDrop( const QString &file )
462 {
463   if ( QgsStyle::isXmlStyleFile( file ) )
464   {
465     QgsStyleExportImportDialog dlg( QgsStyle::defaultStyle(), QgisApp::instance(), QgsStyleExportImportDialog::Import );
466     dlg.setImportFilePath( file );
467     dlg.exec();
468     return true;
469   }
470   return false;
471 }
472 
473 //
474 // QgsProjectRootDataItem
475 //
476 
QgsProjectRootDataItem(QgsDataItem * parent,const QString & path)477 QgsProjectRootDataItem::QgsProjectRootDataItem( QgsDataItem *parent, const QString &path )
478   : QgsProjectItem( parent, QFileInfo( path ).completeBaseName(), path )
479 {
480   mCapabilities = Qgis::BrowserItemCapability::Collapse | Qgis::BrowserItemCapability::Fertile; // collapse by default to avoid costly population on startup
481   mCapabilities |= Qgis::BrowserItemCapability::ItemRepresentsFile;
482   setState( Qgis::BrowserItemState::NotPopulated );
483 }
484 
485 
createChildren()486 QVector<QgsDataItem *> QgsProjectRootDataItem::createChildren()
487 {
488   QVector<QgsDataItem *> childItems;
489 
490   QgsProject p;
491   if ( !p.read( mPath, QgsProject::ReadFlag::FlagDontResolveLayers | QgsProject::ReadFlag::FlagDontLoadLayouts | QgsProject::ReadFlag::FlagDontStoreOriginalStyles ) )
492   {
493     childItems.append( new QgsErrorItem( nullptr, p.error(), mPath + "/error" ) );
494     return childItems;
495   }
496 
497   // recursively create groups and layer items for project's layer tree
498   std::function<void( QgsDataItem *parentItem, QgsLayerTreeGroup *group )> addNodes;
499   addNodes = [this, &addNodes, &childItems]( QgsDataItem * parentItem, QgsLayerTreeGroup * group )
500   {
501     const QList< QgsLayerTreeNode * > children = group->children();
502     for ( QgsLayerTreeNode *child : children )
503     {
504       switch ( child->nodeType() )
505       {
506         case QgsLayerTreeNode::NodeLayer:
507         {
508           if ( QgsLayerTreeLayer *layerNode = qobject_cast< QgsLayerTreeLayer * >( child ) )
509           {
510             QgsMapLayer *layer = layerNode->layer();
511 #if 0 // TODO
512             QString style;
513             if ( layer )
514             {
515               QString errorMsg;
516               QDomDocument doc( QStringLiteral( "qgis" ) );
517               QgsReadWriteContext context;
518               context.setPathResolver( p.pathResolver() );
519               layer->exportNamedStyle( doc, errorMsg, context );
520               style = doc.toString();
521             }
522 #endif
523 
524             QgsLayerItem *layerItem = new QgsLayerItem( nullptr, layerNode->name(),
525                 layer ? layer->source() : QString(),
526                 layer ? layer->source() : QString(),
527                 layer ? QgsLayerItem::typeFromMapLayer( layer ) : Qgis::BrowserLayerType::NoType,
528                 layer ? layer->providerType() : QString() );
529             layerItem->setState( Qgis::BrowserItemState::Populated ); // children are not expected
530             layerItem->setToolTip( layer ? layer->source() : QString() );
531             if ( parentItem == this )
532               childItems << layerItem;
533             else
534               parentItem->addChildItem( layerItem, true );
535           }
536           break;
537         }
538 
539         case QgsLayerTreeNode::NodeGroup:
540         {
541           if ( QgsLayerTreeGroup *groupNode = qobject_cast< QgsLayerTreeGroup * >( child ) )
542           {
543             QgsProjectLayerTreeGroupItem *groupItem = new QgsProjectLayerTreeGroupItem( nullptr, groupNode->name() );
544             addNodes( groupItem, groupNode );
545             groupItem->setState( Qgis::BrowserItemState::Populated );
546             if ( parentItem == this )
547               childItems << groupItem;
548             else
549               parentItem->addChildItem( groupItem, true );
550           }
551         }
552         break;
553       }
554     }
555   };
556 
557   addNodes( this, p.layerTreeRoot() );
558   return childItems;
559 }
560 
561 
562 //
563 // QgsProjectLayerTreeGroupItem
564 //
565 
QgsProjectLayerTreeGroupItem(QgsDataItem * parent,const QString & name)566 QgsProjectLayerTreeGroupItem::QgsProjectLayerTreeGroupItem( QgsDataItem *parent, const QString &name )
567   : QgsDataCollectionItem( parent, name )
568 {
569   mIconName = QStringLiteral( "mActionFolder.svg" );
570   mCapabilities = Qgis::BrowserItemCapability::NoCapabilities;
571   setToolTip( name );
572 }
573 
574 
575 //
576 // QgsProjectDataItemProvider
577 //
578 
name()579 QString QgsProjectDataItemProvider::name()
580 {
581   return QStringLiteral( "project_item" );
582 }
583 
capabilities() const584 int QgsProjectDataItemProvider::capabilities() const
585 {
586   return QgsDataProvider::File;
587 }
588 
createDataItem(const QString & path,QgsDataItem * parentItem)589 QgsDataItem *QgsProjectDataItemProvider::createDataItem( const QString &path, QgsDataItem *parentItem )
590 {
591   const QFileInfo fileInfo( path );
592   if ( fileInfo.suffix().compare( QLatin1String( "qgs" ), Qt::CaseInsensitive ) == 0 || fileInfo.suffix().compare( QLatin1String( "qgz" ), Qt::CaseInsensitive ) == 0 )
593   {
594     return new QgsProjectRootDataItem( parentItem, path );
595   }
596   return nullptr;
597 }
598 
599 //
600 // QgsBookmarksDataItemProvider
601 //
602 
name()603 QString QgsBookmarksDataItemProvider::name()
604 {
605   return QStringLiteral( "bookmarks_item" );
606 }
607 
capabilities() const608 int QgsBookmarksDataItemProvider::capabilities() const
609 {
610   return QgsDataProvider::Database;
611 }
612 
createDataItem(const QString &,QgsDataItem * parentItem)613 QgsDataItem *QgsBookmarksDataItemProvider::createDataItem( const QString &, QgsDataItem *parentItem )
614 {
615   return new QgsBookmarksItem( parentItem, QObject::tr( "Spatial Bookmarks" ), QgsApplication::bookmarkManager(), QgsProject::instance()->bookmarkManager() );
616 }
617 
QgsBookmarksItem(QgsDataItem * parent,const QString & name,QgsBookmarkManager * applicationManager,QgsBookmarkManager * projectManager)618 QgsBookmarksItem::QgsBookmarksItem( QgsDataItem *parent, const QString &name, QgsBookmarkManager *applicationManager, QgsBookmarkManager *projectManager )
619   : QgsDataCollectionItem( parent, name, QStringLiteral( "bookmarks:" ) )
620 {
621   mType = Qgis::BrowserItemType::Custom;
622   mCapabilities = Qgis::BrowserItemCapability::Fast;
623   mApplicationManager = applicationManager;
624   mProjectManager = projectManager;
625   mIconName = QStringLiteral( "/mActionShowBookmarks.svg" );
626   populate();
627 }
628 
createChildren()629 QVector<QgsDataItem *> QgsBookmarksItem::createChildren()
630 {
631   QVector<QgsDataItem *> children;
632   if ( mApplicationManager )
633     children << new QgsBookmarkManagerItem( this, tr( "User Bookmarks" ), mApplicationManager );
634   if ( mProjectManager )
635     children << new QgsBookmarkManagerItem( this, tr( "Project Bookmarks" ), mProjectManager );
636   return children;
637 }
638 
QgsBookmarkManagerItem(QgsDataItem * parent,const QString & name,QgsBookmarkManager * manager)639 QgsBookmarkManagerItem::QgsBookmarkManagerItem( QgsDataItem *parent, const QString &name, QgsBookmarkManager *manager )
640   : QgsDataCollectionItem( parent, name, QStringLiteral( "bookmarks:%1" ).arg( name.toLower() ) )
641 {
642   mType = Qgis::BrowserItemType::Custom;
643   mCapabilities = Qgis::BrowserItemCapability::Fast;
644   mManager = manager;
645   mIconName = QStringLiteral( "/mIconFolder.svg" );
646 
647   connect( mManager, &QgsBookmarkManager::bookmarkAdded, this, [ = ]( const QString & id )
648   {
649     const QgsBookmark newDetails = mManager->bookmarkById( id );
650     if ( newDetails.group().isEmpty() )
651       addChildItem( new QgsBookmarkItem( this, newDetails.name(), newDetails, mManager ), true );
652     else
653     {
654       if ( QgsBookmarkGroupItem *newGroup = groupItem( newDetails.group() ) )
655       {
656         // existing group, add this bookmark to it
657         newGroup->addBookmark( newDetails );
658       }
659       else
660       {
661         // need to create a new group for this (will automatically add the new bookmark)
662         addChildItem( new QgsBookmarkGroupItem( this, newDetails.group(), mManager ), true );
663       }
664     }
665   } );
666   connect( mManager, &QgsBookmarkManager::bookmarkChanged, this, [ = ]( const QString & id )
667   {
668     const QgsBookmark newDetails = mManager->bookmarkById( id );
669 
670     // have to do a deep dive to find the old item...!
671     const QVector<QgsDataItem *> c = children();
672     for ( QgsDataItem *i : c )
673     {
674       if ( QgsBookmarkItem *bookmarkItem = qobject_cast< QgsBookmarkItem * >( i ) )
675       {
676         if ( bookmarkItem->bookmark().id() == id )
677         {
678           // found the target! now, what's changed?
679           if ( bookmarkItem->bookmark().group() == newDetails.group() )
680           {
681             // good, not the group. Just update the existing bookmark then.
682             bookmarkItem->setBookmark( newDetails );
683             return;
684           }
685           else
686           {
687             // group has changed, ouch!
688             // first remove from existing group
689             deleteChildItem( bookmarkItem );
690             // and add a child to the new group
691             if ( QgsBookmarkGroupItem *newGroup = groupItem( newDetails.group() ) )
692             {
693               newGroup->addBookmark( newDetails );
694             }
695             else
696             {
697               // need to create a new group for this (will automatically add the new bookmark)
698               addChildItem( new QgsBookmarkGroupItem( this, newDetails.group(), mManager ), true );
699             }
700           }
701           break;
702         }
703       }
704       else if ( QgsBookmarkGroupItem *group = qobject_cast< QgsBookmarkGroupItem * >( i ) )
705       {
706         if ( QgsBookmarkItem *bookmarkItem = group->childItemById( id ) )
707         {
708           // ok, found old group, now compare
709           if ( bookmarkItem->bookmark().group() == newDetails.group() )
710           {
711             // good, not the group. Just update the existing bookmark then.
712             bookmarkItem->setBookmark( newDetails );
713           }
714           else
715           {
716             // group has changed!
717             // first remove from existing group
718             group->deleteChildItem( bookmarkItem );
719             if ( group->children().empty() )
720               deleteChildItem( group );
721 
722             // and add a child to the new group
723             if ( !newDetails.group().isEmpty() )
724             {
725               if ( QgsBookmarkGroupItem *newGroup = groupItem( newDetails.group() ) )
726               {
727                 newGroup->addBookmark( newDetails );
728               }
729               else
730               {
731                 // need to create a new group for this (will automatically add the new bookmark)
732                 addChildItem( new QgsBookmarkGroupItem( this, newDetails.group(), mManager ), true );
733               }
734             }
735             else
736             {
737               addChildItem( new QgsBookmarkItem( this, newDetails.name(), newDetails, mManager ), true );
738             }
739           }
740           break;
741         }
742       }
743     }
744   } );
745   connect( mManager, &QgsBookmarkManager::bookmarkAboutToBeRemoved, this, [ = ]( const QString & id )
746   {
747     const QgsBookmark b = mManager->bookmarkById( id );
748     if ( !b.group().isEmpty() )
749     {
750       if ( QgsBookmarkGroupItem *group = groupItem( b.group() ) )
751       {
752         group->removeBookmarkChildById( id );
753         if ( group->children().empty() )
754           deleteChildItem( group );
755       }
756     }
757     else if ( QgsBookmarkItem *bookmarkItem = childItemById( id ) )
758     {
759       deleteChildItem( bookmarkItem );
760     }
761   } );
762 
763   populate();
764 }
765 
createChildren()766 QVector<QgsDataItem *> QgsBookmarkManagerItem::createChildren()
767 {
768   QVector<QgsDataItem *> children;
769   const QStringList groupNames = mManager->groups();
770   for ( const QString &group : groupNames )
771   {
772     if ( group.isEmpty() )
773     {
774       const QList<QgsBookmark> matching = mManager->bookmarksByGroup( QString() );
775       for ( const QgsBookmark &bookmark : matching )
776       {
777         children << new QgsBookmarkItem( this, bookmark.name(), bookmark, mManager );
778       }
779     }
780     else
781     {
782       QgsBookmarkGroupItem *item = new QgsBookmarkGroupItem( this, group, mManager );
783       children << item;
784     }
785   }
786   return children;
787 }
788 
groupItem(const QString & group)789 QgsBookmarkGroupItem *QgsBookmarkManagerItem::groupItem( const QString &group )
790 {
791   const QVector<QgsDataItem *> c = children();
792   for ( QgsDataItem *i : c )
793   {
794     if ( QgsBookmarkGroupItem *groupItem = qobject_cast< QgsBookmarkGroupItem * >( i ) )
795     {
796       if ( groupItem->group() == group )
797       {
798         return groupItem;
799       }
800     }
801   }
802   return nullptr;
803 }
804 
childItemById(const QString & id)805 QgsBookmarkItem *QgsBookmarkManagerItem::childItemById( const QString &id )
806 {
807   const QVector<QgsDataItem *> c = children();
808   for ( QgsDataItem *i : c )
809   {
810     if ( QgsBookmarkItem *bookmarkItem = qobject_cast< QgsBookmarkItem * >( i ) )
811     {
812       if ( bookmarkItem->bookmark().id() == id )
813       {
814         return bookmarkItem;
815       }
816     }
817   }
818   return nullptr;
819 }
820 
QgsBookmarkGroupItem(QgsDataItem * parent,const QString & name,QgsBookmarkManager * manager)821 QgsBookmarkGroupItem::QgsBookmarkGroupItem( QgsDataItem *parent, const QString &name, QgsBookmarkManager *manager )
822   : QgsDataCollectionItem( parent, name, QStringLiteral( "bookmarks:%1" ).arg( name.toLower() ) )
823   , mGroup( name )
824 {
825   mType = Qgis::BrowserItemType::Custom;
826   mCapabilities = Qgis::BrowserItemCapability::Fast | Qgis::BrowserItemCapability::Rename;
827   mManager = manager;
828   mIconName = QStringLiteral( "/mIconFolder.svg" );
829   setToolTip( name );
830 
831   setSortKey( QStringLiteral( "  %1" ).arg( name ) );
832 
833   populate();
834 }
835 
createChildren()836 QVector<QgsDataItem *> QgsBookmarkGroupItem::createChildren()
837 {
838   QVector<QgsDataItem *> children;
839   const QList< QgsBookmark > bookmarks = mManager->bookmarksByGroup( mName );
840   children.reserve( bookmarks.size() );
841   for ( const QgsBookmark &bookmark : bookmarks )
842   {
843     children << new QgsBookmarkItem( this, bookmark.name(), bookmark, mManager );
844   }
845   return children;
846 }
847 
addBookmark(const QgsBookmark & bookmark)848 void QgsBookmarkGroupItem::addBookmark( const QgsBookmark &bookmark )
849 {
850   addChildItem( new QgsBookmarkItem( this, bookmark.name(), bookmark, mManager ), true );
851 }
852 
childItemById(const QString & id)853 QgsBookmarkItem *QgsBookmarkGroupItem::childItemById( const QString &id )
854 {
855   const QVector<QgsDataItem *> c = children();
856   for ( QgsDataItem *i : c )
857   {
858     if ( QgsBookmarkItem *bookmarkItem = qobject_cast< QgsBookmarkItem * >( i ) )
859     {
860       if ( bookmarkItem->bookmark().id() == id )
861       {
862         return bookmarkItem;
863       }
864     }
865   }
866   return nullptr;
867 }
868 
removeBookmarkChildById(const QString & id)869 void QgsBookmarkGroupItem::removeBookmarkChildById( const QString &id )
870 {
871   if ( QgsBookmarkItem *bookmarkItem = childItemById( id ) )
872     deleteChildItem( bookmarkItem );
873 }
874 
QgsBookmarkItem(QgsDataItem * parent,const QString & name,const QgsBookmark & bookmark,QgsBookmarkManager * manager)875 QgsBookmarkItem::QgsBookmarkItem( QgsDataItem *parent, const QString &name, const QgsBookmark &bookmark, QgsBookmarkManager *manager )
876   : QgsDataItem( Qgis::BrowserItemType::Custom, parent, name, QStringLiteral( "bookmarks:%1/%2" ).arg( bookmark.group().toLower(), bookmark.id() ) )
877   , mManager( manager )
878   , mBookmark( bookmark )
879 {
880   mType = Qgis::BrowserItemType::Custom;
881   mCapabilities = Qgis::BrowserItemCapability::Rename;
882   mIconName = QStringLiteral( "/mItemBookmark.svg" );
883   setToolTip( name );
884   setState( Qgis::BrowserItemState::Populated ); // no more children
885 }
886 
setBookmark(const QgsBookmark & bookmark)887 void QgsBookmarkItem::setBookmark( const QgsBookmark &bookmark )
888 {
889   setName( bookmark.name() );
890   setToolTip( bookmark.name() );
891   mBookmark = bookmark;
892 }
893 
customUriProviderKey() const894 QString QgsBookmarkDropHandler::customUriProviderKey() const
895 {
896   return QStringLiteral( "bookmark" );
897 }
898 
canHandleCustomUriCanvasDrop(const QgsMimeDataUtils::Uri & uri,QgsMapCanvas *)899 bool QgsBookmarkDropHandler::canHandleCustomUriCanvasDrop( const QgsMimeDataUtils::Uri &uri, QgsMapCanvas * )
900 {
901   return uri.providerKey == customUriProviderKey();
902 }
903 
handleCustomUriCanvasDrop(const QgsMimeDataUtils::Uri & uri,QgsMapCanvas * canvas) const904 bool QgsBookmarkDropHandler::handleCustomUriCanvasDrop( const QgsMimeDataUtils::Uri &uri, QgsMapCanvas *canvas ) const
905 {
906   QDomDocument doc;
907   doc.setContent( uri.uri );
908   const QDomElement elem = doc.documentElement();
909   const QgsBookmark b = QgsBookmark::fromXml( elem, doc );
910 
911   try
912   {
913     if ( ! canvas->setReferencedExtent( b.extent() ) )
914     {
915       QgisApp::instance()->messageBar()->pushWarning( tr( "Zoom to Bookmark" ), tr( "Bookmark extent is empty" ) );
916     }
917     else
918     {
919       canvas->refresh();
920     }
921   }
922   catch ( QgsCsException & )
923   {
924     QgisApp::instance()->messageBar()->pushWarning( tr( "Zoom to Bookmark" ), tr( "Could not reproject bookmark extent to canvas CRS." ) );
925   }
926   return true;
927 }
928 
name()929 QString QgsBookmarksItemGuiProvider::name()
930 {
931   return QStringLiteral( "bookmark_item" );
932 }
933 
acceptDrop(QgsDataItem * item,QgsDataItemGuiContext)934 bool QgsBookmarksItemGuiProvider::acceptDrop( QgsDataItem *item, QgsDataItemGuiContext )
935 {
936   if ( qobject_cast< QgsBookmarkManagerItem * >( item ) )
937     return true;
938 
939   if ( qobject_cast< QgsBookmarkGroupItem * >( item ) )
940     return true;
941 
942   return false;
943 }
944 
handleDrop(QgsDataItem * item,QgsDataItemGuiContext,const QMimeData * data,Qt::DropAction)945 bool QgsBookmarksItemGuiProvider::handleDrop( QgsDataItem *item, QgsDataItemGuiContext, const QMimeData *data, Qt::DropAction )
946 {
947   QgsBookmarkManagerItem *managerItem = qobject_cast< QgsBookmarkManagerItem * >( item );
948   QgsBookmarkGroupItem *groupItem = qobject_cast< QgsBookmarkGroupItem * >( item );
949   if ( managerItem || groupItem )
950   {
951     QgsBookmarkManager *target = managerItem ? managerItem->manager() : groupItem->manager();
952     if ( QgsMimeDataUtils::isUriList( data ) )
953     {
954       const QgsMimeDataUtils::UriList list = QgsMimeDataUtils::decodeUriList( data );
955       for ( const QgsMimeDataUtils::Uri &uri : list )
956       {
957         QDomDocument doc;
958         doc.setContent( uri.uri );
959         const QDomElement elem = doc.documentElement();
960         QgsBookmark b = QgsBookmark::fromXml( elem, doc );
961 
962         if ( !groupItem )
963           b.setGroup( QString() );
964         else
965           b.setGroup( groupItem->group() );
966 
967         // if bookmark doesn't already exist in manager, we add it. Otherwise we update it.
968         if ( target->bookmarkById( b.id() ).id().isEmpty() )
969           target->addBookmark( b );
970         else
971           target->updateBookmark( b );
972       }
973       return true;
974     }
975   }
976 
977   return false;
978 }
979 
populateContextMenu(QgsDataItem * item,QMenu * menu,const QList<QgsDataItem * > & selectedItems,QgsDataItemGuiContext context)980 void QgsBookmarksItemGuiProvider::populateContextMenu( QgsDataItem *item, QMenu *menu, const QList<QgsDataItem *> &selectedItems, QgsDataItemGuiContext context )
981 {
982   if ( qobject_cast< QgsBookmarksItem * >( item ) )
983   {
984     QAction *addBookmark = new QAction( tr( "New Spatial Bookmark…" ), menu );
985     connect( addBookmark, &QAction::triggered, this, [ = ]
986     {
987       QgisApp::instance()->newBookmark();
988     } );
989     menu->addAction( addBookmark );
990     QAction *showBookmarksPanel = new QAction( tr( "Show Spatial Bookmarks Manager" ), menu );
991     connect( showBookmarksPanel, &QAction::triggered, this, [ = ]
992     {
993       QgisApp::instance()->showBookmarkManager( true );
994     } );
995     menu->addAction( showBookmarksPanel );
996     menu->addSeparator();
997     QAction *importBookmarks = new QAction( QgsApplication::getThemeIcon( QStringLiteral( "/mActionSharingImport.svg" ) ), tr( "Import Spatial Bookmarks…" ), menu );
998     connect( importBookmarks, &QAction::triggered, this, [ = ]
999     {
1000       importBookmarksToManager( QgsApplication::bookmarkManager(), context.messageBar() );
1001     } );
1002     menu->addAction( importBookmarks );
1003     QAction *exportBookmarks = new QAction( QgsApplication::getThemeIcon( QStringLiteral( "/mActionSharingExport.svg" ) ), tr( "Export Spatial Bookmarks…" ), menu );
1004     connect( exportBookmarks, &QAction::triggered, this, [ = ]
1005     {
1006       exportBookmarksFromManagers( QList< const QgsBookmarkManager * >() << QgsApplication::bookmarkManager() << QgsProject::instance()->bookmarkManager(), context.messageBar() );
1007     } );
1008     menu->addAction( exportBookmarks );
1009   }
1010   else if ( QgsBookmarkManagerItem *managerItem = qobject_cast< QgsBookmarkManagerItem * >( item ) )
1011   {
1012     QAction *addBookmark = new QAction( tr( "New Spatial Bookmark…" ), menu );
1013     const bool inProject = managerItem->manager() != QgsApplication::bookmarkManager();
1014     connect( addBookmark, &QAction::triggered, this, [ = ]
1015     {
1016       QgisApp::instance()->newBookmark( inProject );
1017     } );
1018     menu->addAction( addBookmark );
1019     menu->addSeparator();
1020     QAction *importBookmarks = new QAction( QgsApplication::getThemeIcon( QStringLiteral( "/mActionSharingImport.svg" ) ), tr( "Import Spatial Bookmarks…" ), menu );
1021     connect( importBookmarks, &QAction::triggered, this, [ = ]
1022     {
1023       importBookmarksToManager( managerItem->manager(), context.messageBar() );
1024     } );
1025     menu->addAction( importBookmarks );
1026 
1027     QAction *exportBookmarks = new QAction( QgsApplication::getThemeIcon( QStringLiteral( "/mActionSharingExport.svg" ) ), tr( "Export Spatial Bookmarks…" ), menu );
1028     connect( exportBookmarks, &QAction::triggered, this, [ = ]
1029     {
1030       exportBookmarksFromManagers( QList< const QgsBookmarkManager * >() << managerItem->manager(), context.messageBar() );
1031     } );
1032     menu->addAction( exportBookmarks );
1033   }
1034   else if ( QgsBookmarkItem *bookmarkItem = qobject_cast< QgsBookmarkItem * >( item ) )
1035   {
1036     QAction *actionZoom = new QAction( QgsApplication::getThemeIcon( QStringLiteral( "/mActionZoomToLayer.svg" ) ), tr( "Zoom to Bookmark" ), menu );
1037     connect( actionZoom, &QAction::triggered, this, [bookmarkItem, context]
1038     {
1039       try
1040       {
1041         if ( !QgisApp::instance()->mapCanvas()->setReferencedExtent( bookmarkItem->bookmark().extent() ) )
1042         {
1043           context.messageBar()->pushWarning( tr( "Zoom to Bookmark" ), tr( "Bookmark extent is empty" ) );
1044         }
1045         else
1046         {
1047           QgisApp::instance()->mapCanvas()->refresh();
1048         }
1049       }
1050       catch ( QgsCsException & )
1051       {
1052         context.messageBar()->pushWarning( tr( "Zoom to Bookmark" ), tr( "Could not reproject bookmark extent to project CRS." ) );
1053       }
1054     } );
1055     menu->addAction( actionZoom );
1056     menu->addSeparator();
1057 
1058     QAction *actionEdit = new QAction( tr( "Edit Spatial Bookmark…" ), menu );
1059     connect( actionEdit, &QAction::triggered, this, [bookmarkItem]
1060     {
1061       QgsBookmarkEditorDialog *dlg = new QgsBookmarkEditorDialog( bookmarkItem->bookmark(), bookmarkItem->manager() == QgsProject::instance()->bookmarkManager(), QgisApp::instance(), QgisApp::instance()->mapCanvas() );
1062       dlg->setAttribute( Qt::WA_DeleteOnClose );
1063       dlg->show();
1064     } );
1065     menu->addAction( actionEdit );
1066 
1067     QStringList ids;
1068     for ( QgsDataItem *i : selectedItems )
1069     {
1070       if ( QgsBookmarkItem *b = qobject_cast< QgsBookmarkItem * >( i ) )
1071       {
1072         if ( b->manager() == bookmarkItem->manager() )
1073           ids << b->bookmark().id();
1074       }
1075     }
1076 
1077     QAction *actionDelete = new QAction( selectedItems.count() == 1 ? tr( "Delete Spatial Bookmark" ) : tr( "Delete Spatial Bookmarks" ), menu );
1078     connect( actionDelete, &QAction::triggered, this, [bookmarkItem, ids]
1079     {
1080       if ( ids.count() == 1 )
1081       {
1082         if ( QMessageBox::question( nullptr, QObject::tr( "Delete Spatial Bookmark" ),
1083                                     QObject::tr( "Are you sure you want to delete the %1 bookmark?" ).arg( bookmarkItem->name() ),
1084                                     QMessageBox::Yes | QMessageBox::No, QMessageBox::No ) != QMessageBox::Yes )
1085           return;
1086 
1087         bookmarkItem->manager()->removeBookmark( bookmarkItem->bookmark().id() );
1088       }
1089       else
1090       {
1091         if ( QMessageBox::question( nullptr, QObject::tr( "Delete Spatial Bookmarks" ),
1092                                     QObject::tr( "Are you sure you want to delete the %1 selected bookmarks?" ).arg( ids.count() ),
1093                                     QMessageBox::Yes | QMessageBox::No, QMessageBox::No ) != QMessageBox::Yes )
1094           return;
1095 
1096         for ( const QString &id : ids )
1097           bookmarkItem->manager()->removeBookmark( id );
1098       }
1099     } );
1100     menu->addAction( actionDelete );
1101   }
1102   else if ( QgsBookmarkGroupItem *groupItem = qobject_cast< QgsBookmarkGroupItem * >( item ) )
1103   {
1104     QStringList groups;
1105     QgsBookmarkManager *manager = groupItem->manager();
1106     for ( QgsDataItem *i : selectedItems )
1107     {
1108       if ( QgsBookmarkGroupItem *g = qobject_cast< QgsBookmarkGroupItem * >( i ) )
1109       {
1110         if ( g->manager() == manager )
1111           groups << g->group();
1112       }
1113     }
1114 
1115     QAction *exportBookmarks = new QAction( QgsApplication::getThemeIcon( QStringLiteral( "/mActionSharingExport.svg" ) ), tr( "Export Spatial Bookmarks…" ), menu );
1116     connect( exportBookmarks, &QAction::triggered, this, [ = ]
1117     {
1118       exportBookmarksFromManagers( QList< const QgsBookmarkManager * >() << groupItem->manager(), context.messageBar(), groupItem->group() );
1119     } );
1120     menu->addAction( exportBookmarks );
1121     menu->addSeparator();
1122 
1123     QAction *actionDelete = new QAction( selectedItems.count() == 1 ? tr( "Delete Bookmark Group" ) : tr( "Delete Bookmark Groups" ), menu );
1124     connect( actionDelete, &QAction::triggered, this, [selectedItems, groups, manager]
1125     {
1126       if ( groups.count() == 1 )
1127       {
1128         if ( QMessageBox::question( nullptr, QObject::tr( "Delete Bookmark Group" ),
1129                                     QObject::tr( "Are you sure you want to delete the %1 bookmark group? This will delete all bookmarks in this group." ).arg( groups.at( 0 ) ),
1130                                     QMessageBox::Yes | QMessageBox::No, QMessageBox::No ) != QMessageBox::Yes )
1131           return;
1132 
1133         const QList<QgsBookmark> matching = manager->bookmarksByGroup( groups.at( 0 ) );
1134         for ( const QgsBookmark &bookmark : matching )
1135         {
1136           manager->removeBookmark( bookmark.id() );
1137         }
1138       }
1139       else
1140       {
1141         if ( QMessageBox::question( nullptr, QObject::tr( "Delete Bookmark Groups" ),
1142                                     QObject::tr( "Are you sure you want to delete the %1 selected bookmark groups? This will delete all bookmarks in these groups." ).arg( groups.count() ),
1143                                     QMessageBox::Yes | QMessageBox::No, QMessageBox::No ) != QMessageBox::Yes )
1144           return;
1145 
1146         int i = 0;
1147         for ( const QString &g : groups )
1148         {
1149           const QList<QgsBookmark> matching = manager->bookmarksByGroup( g );
1150           for ( const QgsBookmark &bookmark : matching )
1151           {
1152             manager->removeBookmark( bookmark.id() );
1153           }
1154           i++;
1155         }
1156       }
1157     } );
1158     menu->addAction( actionDelete );
1159   }
1160 }
1161 
handleDoubleClick(QgsDataItem * item,QgsDataItemGuiContext context)1162 bool QgsBookmarksItemGuiProvider::handleDoubleClick( QgsDataItem *item, QgsDataItemGuiContext context )
1163 {
1164   if ( QgsBookmarkItem *bookmarkItem = qobject_cast< QgsBookmarkItem * >( item ) )
1165   {
1166     try
1167     {
1168       if ( !QgisApp::instance()->mapCanvas()->setReferencedExtent( bookmarkItem->bookmark().extent() ) )
1169       {
1170         context.messageBar()->pushWarning( tr( "Zoom to Bookmark" ), tr( "Bookmark extent is empty" ) );
1171       }
1172       else
1173       {
1174         QgisApp::instance()->mapCanvas()->refresh();
1175       }
1176     }
1177     catch ( QgsCsException & )
1178     {
1179       context.messageBar()->pushWarning( tr( "Zoom to Bookmark" ), tr( "Could not reproject bookmark extent to project CRS." ) );
1180     }
1181     return true;
1182   }
1183   return false;
1184 }
1185 
rename(QgsDataItem * item,const QString & name,QgsDataItemGuiContext context)1186 bool QgsBookmarksItemGuiProvider::rename( QgsDataItem *item, const QString &name, QgsDataItemGuiContext context )
1187 {
1188   if ( QgsBookmarkItem *bookmarkItem = qobject_cast< QgsBookmarkItem * >( item ) )
1189   {
1190     QgsBookmark bookmark = bookmarkItem->bookmark();
1191     bookmark.setName( name );
1192     if ( !bookmarkItem->manager()->updateBookmark( bookmark ) )
1193     {
1194       context.messageBar()->pushWarning( tr( "Rename Bookmark" ), tr( "Could not rename bookmark" ) );
1195       return true;
1196     }
1197     return true;
1198   }
1199   else if ( QgsBookmarkGroupItem *groupItem = qobject_cast< QgsBookmarkGroupItem * >( item ) )
1200   {
1201     groupItem->manager()->renameGroup( groupItem->group(), name );
1202     return true;
1203   }
1204   return false;
1205 }
1206 
exportBookmarksFromManagers(const QList<const QgsBookmarkManager * > & managers,QgsMessageBar * messageBar,const QString & group)1207 void QgsBookmarksItemGuiProvider::exportBookmarksFromManagers( const QList<const QgsBookmarkManager *> &managers, QgsMessageBar *messageBar, const QString &group )
1208 {
1209   QgsSettings settings;
1210 
1211   const QString lastUsedDir = settings.value( QStringLiteral( "Windows/Bookmarks/LastUsedDirectory" ), QDir::homePath() ).toString();
1212   QString fileName = QFileDialog::getSaveFileName( QgisApp::instance(), tr( "Export Bookmarks" ), lastUsedDir,
1213                      tr( "XML files (*.xml *.XML)" ) );
1214   if ( fileName.isEmpty() )
1215   {
1216     return;
1217   }
1218 
1219   // ensure the user never omitted the extension from the file name
1220   fileName = QgsFileUtils::ensureFileNameHasExtension( fileName, QStringList() << QStringLiteral( "xml" ) );
1221 
1222   if ( !QgsBookmarkManager::exportToFile( fileName, managers, group ) )
1223   {
1224     messageBar->pushWarning( tr( "Export Bookmarks" ), tr( "Error exporting bookmark file" ) );
1225   }
1226   else
1227   {
1228     messageBar->pushSuccess( tr( "Export Bookmarks" ), tr( "Successfully exported bookmarks to <a href=\"%1\">%2</a>" )
1229                              .arg( QUrl::fromLocalFile( fileName ).toString(), QDir::toNativeSeparators( fileName ) ) );
1230   }
1231 
1232   settings.setValue( QStringLiteral( "Windows/Bookmarks/LastUsedDirectory" ), QFileInfo( fileName ).path() );
1233 }
1234 
importBookmarksToManager(QgsBookmarkManager * manager,QgsMessageBar * messageBar)1235 void QgsBookmarksItemGuiProvider::importBookmarksToManager( QgsBookmarkManager *manager, QgsMessageBar *messageBar )
1236 {
1237   QgsSettings settings;
1238 
1239   const QString lastUsedDir = settings.value( QStringLiteral( "Windows/Bookmarks/LastUsedDirectory" ), QDir::homePath() ).toString();
1240   const QString fileName = QFileDialog::getOpenFileName( QgisApp::instance(), tr( "Import Bookmarks" ), lastUsedDir,
1241                            tr( "XML files (*.xml *.XML)" ) );
1242   if ( fileName.isEmpty() )
1243   {
1244     return;
1245   }
1246 
1247   if ( !manager->importFromFile( fileName ) )
1248   {
1249     messageBar->pushWarning( tr( "Import Bookmarks" ), tr( "Error importing bookmark file" ) );
1250   }
1251   else
1252   {
1253     messageBar->pushSuccess( tr( "Import Bookmarks" ), tr( "Bookmarks imported successfully" ) );
1254   }
1255   settings.setValue( QStringLiteral( "Windows/Bookmarks/LastUsedDirectory" ), QFileInfo( fileName ).path() );
1256 }
1257 
1258 //
1259 // QgsHtmlDataItemProvider
1260 //
1261 
name()1262 QString QgsHtmlDataItemProvider::name()
1263 {
1264   return QStringLiteral( "html" );
1265 }
1266 
capabilities() const1267 int QgsHtmlDataItemProvider::capabilities() const
1268 {
1269   return QgsDataProvider::File;
1270 }
1271 
createDataItem(const QString & path,QgsDataItem * parentItem)1272 QgsDataItem *QgsHtmlDataItemProvider::createDataItem( const QString &path, QgsDataItem *parentItem )
1273 {
1274   const QFileInfo fileInfo( path );
1275 
1276   if ( fileInfo.suffix().compare( QLatin1String( "htm" ), Qt::CaseInsensitive ) == 0
1277        || fileInfo.suffix().compare( QLatin1String( "html" ), Qt::CaseInsensitive ) == 0 )
1278   {
1279     return new QgsHtmlDataItem( parentItem, fileInfo.fileName(), path );
1280   }
1281   return nullptr;
1282 }
1283 
1284 //
1285 // QgsHtmlDataItemProvider
1286 //
1287 
QgsHtmlDataItem(QgsDataItem * parent,const QString & name,const QString & path)1288 QgsHtmlDataItem::QgsHtmlDataItem( QgsDataItem *parent, const QString &name, const QString &path )
1289   : QgsDataItem( Qgis::BrowserItemType::Custom, parent, name, path )
1290 {
1291   setState( Qgis::BrowserItemState::Populated ); // no children
1292   setIconName( QStringLiteral( "/mIconHtml.svg" ) );
1293   setToolTip( QDir::toNativeSeparators( path ) );
1294   mCapabilities |= Qgis::BrowserItemCapability::ItemRepresentsFile;
1295 }
1296 
handleDoubleClick()1297 bool QgsHtmlDataItem::handleDoubleClick()
1298 {
1299   QDesktopServices::openUrl( QUrl::fromLocalFile( path() ) );
1300   return true;
1301 }
1302 
actions(QWidget * parent)1303 QList<QAction *> QgsHtmlDataItem::actions( QWidget *parent )
1304 {
1305   QAction *openAction = new QAction( tr( "&Open File…" ), parent );
1306   connect( openAction, &QAction::triggered, this, [ = ]
1307   {
1308     QDesktopServices::openUrl( QUrl::fromLocalFile( path() ) );
1309   } );
1310   return QList<QAction *>() << openAction;
1311 }
1312