Skip to content

Commit 09c2daa

Browse files
committedNov 1, 2018
[FEATURE] Allow exploration of QGS project file contents directly
within browser Allows QGIS project file items inside the browser to be expanded, showing the full layer tree (including groups) contained within that project. Layers are shown as normal layer items, allowing them to be easily added to the current project via drag and drop or double click. Additionally, because they are treated just the same as any other layer items in the browser, they can be drag and dropped within the browser to e.g. directly copy the layer to a geopackage file! TODO: apply layer symbology from project file when adding a layer from a different project to the current project
1 parent f0436df commit 09c2daa

File tree

6 files changed

+312
-1
lines changed

6 files changed

+312
-1
lines changed
 

‎src/app/qgisapp.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1161,6 +1161,8 @@ QgisApp::QgisApp( QSplashScreen *splash, bool restorePlugins, bool skipVersionCh
11611161
registerCustomDropHandler( new QgsPyDropHandler() );
11621162
#endif
11631163

1164+
QgsApplication::dataItemProviderRegistry()->addProvider( new QgsProjectDataItemProvider() );
1165+
11641166
// Create the plugin registry and load plugins
11651167
// load any plugins that were running in the last session
11661168
mSplash->showMessage( tr( "Restoring loaded plugins" ), Qt::AlignHCenter | Qt::AlignBottom );

‎src/app/qgsappbrowserproviders.cpp

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
#include "qgisapp.h"
1818
#include "qgsstyleexportimportdialog.h"
1919
#include "qgsstyle.h"
20+
#include "qgslayertreenode.h"
21+
#include "qgslayertree.h"
2022
#include <QDesktopServices>
2123

2224
//
@@ -399,3 +401,127 @@ bool QgsStyleXmlDropHandler::handleFileDrop( const QString &file )
399401
return false;
400402
}
401403

404+
//
405+
// QgsProjectRootDataItem
406+
//
407+
408+
QgsProjectRootDataItem::QgsProjectRootDataItem( QgsDataItem *parent, const QString &path )
409+
: QgsProjectItem( parent, QFileInfo( path ).completeBaseName(), path )
410+
{
411+
mCapabilities = Collapse | Fertile; // collapse by default to avoid costly population on startup
412+
setState( NotPopulated );
413+
}
414+
415+
416+
QVector<QgsDataItem *> QgsProjectRootDataItem::createChildren()
417+
{
418+
QVector<QgsDataItem *> childItems;
419+
420+
QgsProject p;
421+
if ( !p.read( mPath ) )
422+
{
423+
childItems.append( new QgsErrorItem( nullptr, p.error(), mPath + "/error" ) );
424+
return childItems;
425+
}
426+
427+
// recursively create groups and layer items for project's layer tree
428+
std::function<void( QgsDataItem *parentItem, QgsLayerTreeGroup *group )> addNodes;
429+
addNodes = [this, &addNodes, &childItems]( QgsDataItem * parentItem, QgsLayerTreeGroup * group )
430+
{
431+
const QList< QgsLayerTreeNode * > children = group->children();
432+
for ( QgsLayerTreeNode *child : children )
433+
{
434+
switch ( child->nodeType() )
435+
{
436+
case QgsLayerTreeNode::NodeLayer:
437+
{
438+
if ( QgsLayerTreeLayer *layerNode = qobject_cast< QgsLayerTreeLayer * >( child ) )
439+
{
440+
QgsMapLayer *layer = layerNode->layer();
441+
#if 0 // TODO
442+
QString style;
443+
if ( layer )
444+
{
445+
QString errorMsg;
446+
QDomDocument doc( QStringLiteral( "qgis" ) );
447+
QgsReadWriteContext context;
448+
context.setPathResolver( p.pathResolver() );
449+
layer->exportNamedStyle( doc, errorMsg, context );
450+
style = doc.toString();
451+
}
452+
#endif
453+
454+
QgsLayerItem *layerItem = new QgsLayerItem( nullptr, layerNode->name(),
455+
layer ? layer->source() : QString(),
456+
layer ? layer->source() : QString(),
457+
layer ? QgsLayerItem::typeFromMapLayer( layer ) : QgsLayerItem::NoType,
458+
layer ? layer->dataProvider()->name() : QString() );
459+
layerItem->setState( Populated ); // children are not expected
460+
layerItem->setToolTip( layer ? layer->source() : QString() );
461+
if ( parentItem == this )
462+
childItems << layerItem;
463+
else
464+
parentItem->addChildItem( layerItem, true );
465+
}
466+
break;
467+
}
468+
469+
case QgsLayerTreeNode::NodeGroup:
470+
{
471+
if ( QgsLayerTreeGroup *groupNode = qobject_cast< QgsLayerTreeGroup * >( child ) )
472+
{
473+
QgsProjectLayerTreeGroupItem *groupItem = new QgsProjectLayerTreeGroupItem( nullptr, groupNode->name() );
474+
addNodes( groupItem, groupNode );
475+
groupItem->setState( Populated );
476+
if ( parentItem == this )
477+
childItems << groupItem;
478+
else
479+
parentItem->addChildItem( groupItem, true );
480+
}
481+
}
482+
break;
483+
}
484+
}
485+
};
486+
487+
addNodes( this, p.layerTreeRoot() );
488+
return childItems;
489+
}
490+
491+
492+
//
493+
// QgsProjectLayerTreeGroupItem
494+
//
495+
496+
QgsProjectLayerTreeGroupItem::QgsProjectLayerTreeGroupItem( QgsDataItem *parent, const QString &name )
497+
: QgsDataCollectionItem( parent, name )
498+
{
499+
mIconName = QStringLiteral( "mActionFolder.svg" );
500+
mCapabilities = NoCapabilities;
501+
setToolTip( name );
502+
}
503+
504+
505+
//
506+
// QgsProjectDataItemProvider
507+
//
508+
509+
QString QgsProjectDataItemProvider::name()
510+
{
511+
return QStringLiteral( "project_item" );
512+
}
513+
514+
int QgsProjectDataItemProvider::capabilities()
515+
{
516+
return QgsDataProvider::File;
517+
}
518+
519+
QgsDataItem *QgsProjectDataItemProvider::createDataItem( const QString &path, QgsDataItem *parentItem )
520+
{
521+
QFileInfo fileInfo( path );
522+
if ( fileInfo.suffix().compare( QLatin1String( "qgs" ), Qt::CaseInsensitive ) == 0 || fileInfo.suffix().compare( QLatin1String( "qgz" ), Qt::CaseInsensitive ) == 0 )
523+
{
524+
return new QgsProjectRootDataItem( parentItem, path );
525+
}
526+
return nullptr;
527+
}

‎src/app/qgsappbrowserproviders.h

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#ifndef QGSAPPBROWSERPROVIDERS_H
1616
#define QGSAPPBROWSERPROVIDERS_H
1717

18+
#include "qgis_app.h"
1819
#include "qgsdataitemprovider.h"
1920
#include "qgsdataprovider.h"
2021
#include "qgscustomdrophandler.h"
@@ -189,4 +190,48 @@ class QgsStyleXmlDropHandler : public QgsCustomDropHandler
189190
bool handleFileDrop( const QString &file ) override;
190191
};
191192

193+
/**
194+
* Custom data item for qgs/qgz QGIS project files, with more functionality than default browser project
195+
* file handling. Specifically allows browsing of the project's layer structure within the browser
196+
*/
197+
class APP_EXPORT QgsProjectRootDataItem : public QgsProjectItem
198+
{
199+
public:
200+
201+
/**
202+
* Constructor for QgsProjectRootDataItem, with the specified
203+
* project \a path.
204+
*/
205+
QgsProjectRootDataItem( QgsDataItem *parent, const QString &path );
206+
QVector<QgsDataItem *> createChildren() override;
207+
208+
};
209+
210+
/**
211+
* Represents a layer tree group node within a QGIS project file.
212+
*/
213+
class APP_EXPORT QgsProjectLayerTreeGroupItem : public QgsDataCollectionItem
214+
{
215+
public:
216+
217+
/**
218+
* Constructor for QgsProjectLayerTreeGroupItem, with the specified group \a name.
219+
*/
220+
QgsProjectLayerTreeGroupItem( QgsDataItem *parent, const QString &name );
221+
222+
};
223+
224+
/**
225+
* Custom data item provider for showing qgs/qgz QGIS project files within the browser,
226+
* including the ability to browser the whole project's layer tree structure directly
227+
* within the browser.
228+
*/
229+
class APP_EXPORT QgsProjectDataItemProvider : public QgsDataItemProvider
230+
{
231+
public:
232+
QString name() override;
233+
int capabilities() override;
234+
QgsDataItem *createDataItem( const QString &path, QgsDataItem *parentItem ) override;
235+
};
236+
192237
#endif // QGSAPPBROWSERPROVIDERS_H

‎src/gui/qgsbrowserdockwidget.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ void QgsBrowserDockWidget::itemDoubleClicked( const QModelIndex &index )
175175
return;
176176
else
177177
{
178-
// double click not handled by browser model, so use as default view expand behavior
178+
// double-click not handled by browser model, so use as default view expand behavior
179179
if ( mBrowserView->isExpanded( index ) )
180180
mBrowserView->collapse( index );
181181
else

‎tests/src/app/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ IF (WITH_BINDINGS)
9393
ADD_QGIS_TEST(apppythontest testqgisapppython.cpp)
9494
ENDIF ()
9595
ADD_QGIS_TEST(qgisapp testqgisapp.cpp)
96+
ADD_QGIS_TEST(appbrowserproviders testqgsappbrowserproviders.cpp)
9697
ADD_QGIS_TEST(qgisappclipboard testqgisappclipboard.cpp)
9798
ADD_QGIS_TEST(attributetabletest testqgsattributetable.cpp)
9899
ADD_QGIS_TEST(applocatorfilters testqgsapplocatorfilters.cpp)
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
/***************************************************************************
2+
testqgsappbrowserproviders.cpp
3+
--------------------------------------
4+
Date : October 30 2018
5+
Copyright : (C) 2018 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+
#include "qgstest.h"
16+
17+
#include <QObject>
18+
#include <QString>
19+
#include <QStringList>
20+
21+
//qgis includes...
22+
#include "qgsdataitem.h"
23+
#include "qgsapplication.h"
24+
#include "qgslogger.h"
25+
#include "qgsdataitemprovider.h"
26+
#include "qgsdataitemproviderregistry.h"
27+
#include "qgssettings.h"
28+
#include "qgsappbrowserproviders.h"
29+
30+
class TestQgsAppBrowserProviders : public QObject
31+
{
32+
Q_OBJECT
33+
34+
public:
35+
TestQgsAppBrowserProviders();
36+
37+
private slots:
38+
void initTestCase();// will be called before the first testfunction is executed.
39+
void cleanupTestCase();// will be called after the last testfunction was executed.
40+
void init() {} // will be called before each testfunction is executed.
41+
void cleanup() {} // will be called after every testfunction.
42+
43+
void testProjectItemCreation();
44+
45+
private:
46+
QgsDirectoryItem *mDirItem = nullptr;
47+
QString mScanItemsSetting;
48+
QString mTestDataDir;
49+
};
50+
51+
TestQgsAppBrowserProviders::TestQgsAppBrowserProviders() = default;
52+
53+
void TestQgsAppBrowserProviders::initTestCase()
54+
{
55+
//
56+
// Runs once before any tests are run
57+
//
58+
// init QGIS's paths - true means that all path will be inited from prefix
59+
QgsApplication::init();
60+
QgsApplication::initQgis();
61+
QgsApplication::showSettings();
62+
63+
QString dataDir( TEST_DATA_DIR ); //defined in CmakeLists.txt
64+
mTestDataDir = dataDir + '/';
65+
66+
// Set up the QgsSettings environment
67+
QCoreApplication::setOrganizationName( QStringLiteral( "QGIS" ) );
68+
QCoreApplication::setOrganizationDomain( QStringLiteral( "qgis.org" ) );
69+
QCoreApplication::setApplicationName( QStringLiteral( "QGIS-TEST" ) );
70+
// save current scanItemsSetting value
71+
QgsSettings settings;
72+
mScanItemsSetting = settings.value( QStringLiteral( "/qgis/scanItemsInBrowser2" ), QVariant( "" ) ).toString();
73+
74+
//create a directory item that will be used in all tests...
75+
mDirItem = new QgsDirectoryItem( nullptr, QStringLiteral( "Test" ), TEST_DATA_DIR );
76+
}
77+
78+
void TestQgsAppBrowserProviders::cleanupTestCase()
79+
{
80+
// restore scanItemsSetting
81+
QgsSettings settings;
82+
settings.setValue( QStringLiteral( "/qgis/scanItemsInBrowser2" ), mScanItemsSetting );
83+
if ( mDirItem )
84+
delete mDirItem;
85+
86+
QgsApplication::exitQgis();
87+
}
88+
89+
90+
void TestQgsAppBrowserProviders::testProjectItemCreation()
91+
{
92+
QgsDirectoryItem *dirItem = new QgsDirectoryItem( nullptr, QStringLiteral( "Test" ), mTestDataDir + QStringLiteral( "qgis_server/" ) );
93+
QVector<QgsDataItem *> children = dirItem->createChildren();
94+
95+
// now, add a specific provider which handles project files
96+
QgsApplication::dataItemProviderRegistry()->addProvider( new QgsProjectDataItemProvider() );
97+
98+
dirItem = new QgsDirectoryItem( nullptr, QStringLiteral( "Test" ), mTestDataDir + QStringLiteral( "qgis_server/" ) );
99+
children = dirItem->createChildren();
100+
101+
for ( QgsDataItem *child : children )
102+
{
103+
if ( child->type() == QgsDataItem::Project && child->path() == mTestDataDir + QStringLiteral( "qgis_server/test_project.qgs" ) )
104+
{
105+
child->populate( true );
106+
107+
QCOMPARE( child->children().count(), 4 );
108+
QVERIFY( dynamic_cast< QgsProjectLayerTreeGroupItem * >( child->children().at( 0 ) ) );
109+
QCOMPARE( child->children().at( 0 )->name(), QStringLiteral( "groupwithoutshortname" ) );
110+
111+
QCOMPARE( child->children().at( 0 )->children().count(), 1 );
112+
QVERIFY( dynamic_cast< QgsLayerItem * >( child->children().at( 0 )->children().at( 0 ) ) );
113+
QCOMPARE( child->children().at( 0 )->children().at( 0 )->name(), QStringLiteral( "testlayer3" ) );
114+
115+
QVERIFY( dynamic_cast< QgsProjectLayerTreeGroupItem * >( child->children().at( 1 ) ) );
116+
QCOMPARE( child->children().at( 1 )->name(), QStringLiteral( "groupwithshortname" ) );
117+
118+
QCOMPARE( child->children().at( 1 )->children().count(), 1 );
119+
QVERIFY( dynamic_cast< QgsLayerItem * >( child->children().at( 1 )->children().at( 0 ) ) );
120+
QCOMPARE( child->children().at( 1 )->children().at( 0 )->name(), QStringLiteral( "testlayer2" ) );
121+
122+
QVERIFY( dynamic_cast< QgsLayerItem * >( child->children().at( 2 ) ) );
123+
QCOMPARE( child->children().at( 2 )->name(), QStringLiteral( "testlayer" ) );
124+
125+
QVERIFY( dynamic_cast< QgsLayerItem * >( child->children().at( 3 ) ) );
126+
QCOMPARE( child->children().at( 3 )->name(), QStringLiteral( "testlayer \u00E8\u00E9" ) );
127+
128+
delete dirItem;
129+
return;
130+
}
131+
}
132+
delete dirItem;
133+
QVERIFY( false ); // should not be reached
134+
}
135+
136+
QGSTEST_MAIN( TestQgsAppBrowserProviders )
137+
#include "testqgsappbrowserproviders.moc"

0 commit comments

Comments
 (0)
Please sign in to comment.