diff --git a/src/app/qgisapp.cpp b/src/app/qgisapp.cpp index 5af333821d2..f8912330a91 100644 --- a/src/app/qgisapp.cpp +++ b/src/app/qgisapp.cpp @@ -1161,6 +1161,8 @@ QgisApp::QgisApp( QSplashScreen *splash, bool restorePlugins, bool skipVersionCh registerCustomDropHandler( new QgsPyDropHandler() ); #endif + QgsApplication::dataItemProviderRegistry()->addProvider( new QgsProjectDataItemProvider() ); + // Create the plugin registry and load plugins // load any plugins that were running in the last session mSplash->showMessage( tr( "Restoring loaded plugins" ), Qt::AlignHCenter | Qt::AlignBottom ); diff --git a/src/app/qgsappbrowserproviders.cpp b/src/app/qgsappbrowserproviders.cpp index c4da43445bc..6bb9998192f 100644 --- a/src/app/qgsappbrowserproviders.cpp +++ b/src/app/qgsappbrowserproviders.cpp @@ -17,6 +17,8 @@ #include "qgisapp.h" #include "qgsstyleexportimportdialog.h" #include "qgsstyle.h" +#include "qgslayertreenode.h" +#include "qgslayertree.h" #include // @@ -399,3 +401,127 @@ bool QgsStyleXmlDropHandler::handleFileDrop( const QString &file ) return false; } +// +// QgsProjectRootDataItem +// + +QgsProjectRootDataItem::QgsProjectRootDataItem( QgsDataItem *parent, const QString &path ) + : QgsProjectItem( parent, QFileInfo( path ).completeBaseName(), path ) +{ + mCapabilities = Collapse | Fertile; // collapse by default to avoid costly population on startup + setState( NotPopulated ); +} + + +QVector QgsProjectRootDataItem::createChildren() +{ + QVector childItems; + + QgsProject p; + if ( !p.read( mPath ) ) + { + childItems.append( new QgsErrorItem( nullptr, p.error(), mPath + "/error" ) ); + return childItems; + } + + // recursively create groups and layer items for project's layer tree + std::function addNodes; + addNodes = [this, &addNodes, &childItems]( QgsDataItem * parentItem, QgsLayerTreeGroup * group ) + { + const QList< QgsLayerTreeNode * > children = group->children(); + for ( QgsLayerTreeNode *child : children ) + { + switch ( child->nodeType() ) + { + case QgsLayerTreeNode::NodeLayer: + { + if ( QgsLayerTreeLayer *layerNode = qobject_cast< QgsLayerTreeLayer * >( child ) ) + { + QgsMapLayer *layer = layerNode->layer(); +#if 0 // TODO + QString style; + if ( layer ) + { + QString errorMsg; + QDomDocument doc( QStringLiteral( "qgis" ) ); + QgsReadWriteContext context; + context.setPathResolver( p.pathResolver() ); + layer->exportNamedStyle( doc, errorMsg, context ); + style = doc.toString(); + } +#endif + + QgsLayerItem *layerItem = new QgsLayerItem( nullptr, layerNode->name(), + layer ? layer->source() : QString(), + layer ? layer->source() : QString(), + layer ? QgsLayerItem::typeFromMapLayer( layer ) : QgsLayerItem::NoType, + layer ? layer->dataProvider()->name() : QString() ); + layerItem->setState( Populated ); // children are not expected + layerItem->setToolTip( layer ? layer->source() : QString() ); + if ( parentItem == this ) + childItems << layerItem; + else + parentItem->addChildItem( layerItem, true ); + } + break; + } + + case QgsLayerTreeNode::NodeGroup: + { + if ( QgsLayerTreeGroup *groupNode = qobject_cast< QgsLayerTreeGroup * >( child ) ) + { + QgsProjectLayerTreeGroupItem *groupItem = new QgsProjectLayerTreeGroupItem( nullptr, groupNode->name() ); + addNodes( groupItem, groupNode ); + groupItem->setState( Populated ); + if ( parentItem == this ) + childItems << groupItem; + else + parentItem->addChildItem( groupItem, true ); + } + } + break; + } + } + }; + + addNodes( this, p.layerTreeRoot() ); + return childItems; +} + + +// +// QgsProjectLayerTreeGroupItem +// + +QgsProjectLayerTreeGroupItem::QgsProjectLayerTreeGroupItem( QgsDataItem *parent, const QString &name ) + : QgsDataCollectionItem( parent, name ) +{ + mIconName = QStringLiteral( "mActionFolder.svg" ); + mCapabilities = NoCapabilities; + setToolTip( name ); +} + + +// +// QgsProjectDataItemProvider +// + +QString QgsProjectDataItemProvider::name() +{ + return QStringLiteral( "project_item" ); +} + +int QgsProjectDataItemProvider::capabilities() +{ + return QgsDataProvider::File; +} + +QgsDataItem *QgsProjectDataItemProvider::createDataItem( const QString &path, QgsDataItem *parentItem ) +{ + QFileInfo fileInfo( path ); + if ( fileInfo.suffix().compare( QLatin1String( "qgs" ), Qt::CaseInsensitive ) == 0 || fileInfo.suffix().compare( QLatin1String( "qgz" ), Qt::CaseInsensitive ) == 0 ) + { + return new QgsProjectRootDataItem( parentItem, path ); + } + return nullptr; +} diff --git a/src/app/qgsappbrowserproviders.h b/src/app/qgsappbrowserproviders.h index aec7ecf44b9..c533af63aa1 100644 --- a/src/app/qgsappbrowserproviders.h +++ b/src/app/qgsappbrowserproviders.h @@ -15,6 +15,7 @@ #ifndef QGSAPPBROWSERPROVIDERS_H #define QGSAPPBROWSERPROVIDERS_H +#include "qgis_app.h" #include "qgsdataitemprovider.h" #include "qgsdataprovider.h" #include "qgscustomdrophandler.h" @@ -189,4 +190,48 @@ class QgsStyleXmlDropHandler : public QgsCustomDropHandler bool handleFileDrop( const QString &file ) override; }; +/** + * Custom data item for qgs/qgz QGIS project files, with more functionality than default browser project + * file handling. Specifically allows browsing of the project's layer structure within the browser + */ +class APP_EXPORT QgsProjectRootDataItem : public QgsProjectItem +{ + public: + + /** + * Constructor for QgsProjectRootDataItem, with the specified + * project \a path. + */ + QgsProjectRootDataItem( QgsDataItem *parent, const QString &path ); + QVector createChildren() override; + +}; + +/** + * Represents a layer tree group node within a QGIS project file. + */ +class APP_EXPORT QgsProjectLayerTreeGroupItem : public QgsDataCollectionItem +{ + public: + + /** + * Constructor for QgsProjectLayerTreeGroupItem, with the specified group \a name. + */ + QgsProjectLayerTreeGroupItem( QgsDataItem *parent, const QString &name ); + +}; + +/** + * Custom data item provider for showing qgs/qgz QGIS project files within the browser, + * including the ability to browser the whole project's layer tree structure directly + * within the browser. + */ +class APP_EXPORT QgsProjectDataItemProvider : public QgsDataItemProvider +{ + public: + QString name() override; + int capabilities() override; + QgsDataItem *createDataItem( const QString &path, QgsDataItem *parentItem ) override; +}; + #endif // QGSAPPBROWSERPROVIDERS_H diff --git a/src/gui/qgsbrowserdockwidget.cpp b/src/gui/qgsbrowserdockwidget.cpp index 3a0670b15ce..420f1e94993 100644 --- a/src/gui/qgsbrowserdockwidget.cpp +++ b/src/gui/qgsbrowserdockwidget.cpp @@ -175,7 +175,7 @@ void QgsBrowserDockWidget::itemDoubleClicked( const QModelIndex &index ) return; else { - // double click not handled by browser model, so use as default view expand behavior + // double-click not handled by browser model, so use as default view expand behavior if ( mBrowserView->isExpanded( index ) ) mBrowserView->collapse( index ); else diff --git a/tests/src/app/CMakeLists.txt b/tests/src/app/CMakeLists.txt index c5a51ac7b92..8e8a5d0666d 100644 --- a/tests/src/app/CMakeLists.txt +++ b/tests/src/app/CMakeLists.txt @@ -93,6 +93,7 @@ IF (WITH_BINDINGS) ADD_QGIS_TEST(apppythontest testqgisapppython.cpp) ENDIF () ADD_QGIS_TEST(qgisapp testqgisapp.cpp) +ADD_QGIS_TEST(appbrowserproviders testqgsappbrowserproviders.cpp) ADD_QGIS_TEST(qgisappclipboard testqgisappclipboard.cpp) ADD_QGIS_TEST(attributetabletest testqgsattributetable.cpp) ADD_QGIS_TEST(applocatorfilters testqgsapplocatorfilters.cpp) diff --git a/tests/src/app/testqgsappbrowserproviders.cpp b/tests/src/app/testqgsappbrowserproviders.cpp new file mode 100644 index 00000000000..b318bab50ae --- /dev/null +++ b/tests/src/app/testqgsappbrowserproviders.cpp @@ -0,0 +1,137 @@ +/*************************************************************************** + testqgsappbrowserproviders.cpp + -------------------------------------- + Date : October 30 2018 + Copyright : (C) 2018 Nyall Dawson + Email : nyall dot dawson at gmail dot com + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +#include "qgstest.h" + +#include +#include +#include + +//qgis includes... +#include "qgsdataitem.h" +#include "qgsapplication.h" +#include "qgslogger.h" +#include "qgsdataitemprovider.h" +#include "qgsdataitemproviderregistry.h" +#include "qgssettings.h" +#include "qgsappbrowserproviders.h" + +class TestQgsAppBrowserProviders : public QObject +{ + Q_OBJECT + + public: + TestQgsAppBrowserProviders(); + + private slots: + void initTestCase();// will be called before the first testfunction is executed. + void cleanupTestCase();// will be called after the last testfunction was executed. + void init() {} // will be called before each testfunction is executed. + void cleanup() {} // will be called after every testfunction. + + void testProjectItemCreation(); + + private: + QgsDirectoryItem *mDirItem = nullptr; + QString mScanItemsSetting; + QString mTestDataDir; +}; + +TestQgsAppBrowserProviders::TestQgsAppBrowserProviders() = default; + +void TestQgsAppBrowserProviders::initTestCase() +{ + // + // Runs once before any tests are run + // + // init QGIS's paths - true means that all path will be inited from prefix + QgsApplication::init(); + QgsApplication::initQgis(); + QgsApplication::showSettings(); + + QString dataDir( TEST_DATA_DIR ); //defined in CmakeLists.txt + mTestDataDir = dataDir + '/'; + + // Set up the QgsSettings environment + QCoreApplication::setOrganizationName( QStringLiteral( "QGIS" ) ); + QCoreApplication::setOrganizationDomain( QStringLiteral( "qgis.org" ) ); + QCoreApplication::setApplicationName( QStringLiteral( "QGIS-TEST" ) ); + // save current scanItemsSetting value + QgsSettings settings; + mScanItemsSetting = settings.value( QStringLiteral( "/qgis/scanItemsInBrowser2" ), QVariant( "" ) ).toString(); + + //create a directory item that will be used in all tests... + mDirItem = new QgsDirectoryItem( nullptr, QStringLiteral( "Test" ), TEST_DATA_DIR ); +} + +void TestQgsAppBrowserProviders::cleanupTestCase() +{ + // restore scanItemsSetting + QgsSettings settings; + settings.setValue( QStringLiteral( "/qgis/scanItemsInBrowser2" ), mScanItemsSetting ); + if ( mDirItem ) + delete mDirItem; + + QgsApplication::exitQgis(); +} + + +void TestQgsAppBrowserProviders::testProjectItemCreation() +{ + QgsDirectoryItem *dirItem = new QgsDirectoryItem( nullptr, QStringLiteral( "Test" ), mTestDataDir + QStringLiteral( "qgis_server/" ) ); + QVector children = dirItem->createChildren(); + + // now, add a specific provider which handles project files + QgsApplication::dataItemProviderRegistry()->addProvider( new QgsProjectDataItemProvider() ); + + dirItem = new QgsDirectoryItem( nullptr, QStringLiteral( "Test" ), mTestDataDir + QStringLiteral( "qgis_server/" ) ); + children = dirItem->createChildren(); + + for ( QgsDataItem *child : children ) + { + if ( child->type() == QgsDataItem::Project && child->path() == mTestDataDir + QStringLiteral( "qgis_server/test_project.qgs" ) ) + { + child->populate( true ); + + QCOMPARE( child->children().count(), 4 ); + QVERIFY( dynamic_cast< QgsProjectLayerTreeGroupItem * >( child->children().at( 0 ) ) ); + QCOMPARE( child->children().at( 0 )->name(), QStringLiteral( "groupwithoutshortname" ) ); + + QCOMPARE( child->children().at( 0 )->children().count(), 1 ); + QVERIFY( dynamic_cast< QgsLayerItem * >( child->children().at( 0 )->children().at( 0 ) ) ); + QCOMPARE( child->children().at( 0 )->children().at( 0 )->name(), QStringLiteral( "testlayer3" ) ); + + QVERIFY( dynamic_cast< QgsProjectLayerTreeGroupItem * >( child->children().at( 1 ) ) ); + QCOMPARE( child->children().at( 1 )->name(), QStringLiteral( "groupwithshortname" ) ); + + QCOMPARE( child->children().at( 1 )->children().count(), 1 ); + QVERIFY( dynamic_cast< QgsLayerItem * >( child->children().at( 1 )->children().at( 0 ) ) ); + QCOMPARE( child->children().at( 1 )->children().at( 0 )->name(), QStringLiteral( "testlayer2" ) ); + + QVERIFY( dynamic_cast< QgsLayerItem * >( child->children().at( 2 ) ) ); + QCOMPARE( child->children().at( 2 )->name(), QStringLiteral( "testlayer" ) ); + + QVERIFY( dynamic_cast< QgsLayerItem * >( child->children().at( 3 ) ) ); + QCOMPARE( child->children().at( 3 )->name(), QStringLiteral( "testlayer \u00E8\u00E9" ) ); + + delete dirItem; + return; + } + } + delete dirItem; + QVERIFY( false ); // should not be reached +} + +QGSTEST_MAIN( TestQgsAppBrowserProviders ) +#include "testqgsappbrowserproviders.moc"