From 5f66f36ff6dd911ba3fca8ec9580e50812f404a0 Mon Sep 17 00:00:00 2001 From: Muhammad Yarjuna Rohmat Date: Mon, 7 Aug 2017 02:24:12 +0700 Subject: [PATCH] [FEATURE] QGIS - GeoNode Integration: Integrate with QGIS Browser Panel (#4816) * add Geonode connection menu to the toolbar * add header files for geonode-qgis client * add action to launch geonode connection dialog from menubar * Move to proper directory * Add geonodeconnection class. * Add unit test for geonode connection. * Use const static to avoid typo. * Get list layers from geonode. * Add get maps method. * Geonode connection dialog (#13) * add new geonode connection dialog * apply functionality to the geonode connection manager dialog * add save and load geonode connection functionality * edit baseKey and credentialBaseKey * remove auto-connect slots * Add unit test for geonode connection. * Add wms url getter. * Add uuid and layer name in the table. * Add handler for the list layer clicked. WIP. * Use new style connect, better hacky to get wms url. * update gitignore * Make QGIS able to add WMS layer from geonode. With hacky code. * Fix Docstring. * Show web service type (WMS/WFS) in layer table. * fix http and toolbar menu * add geonode data item to the browser panel as an extention of ows provider * [WIP] Add WFS. * Add geonode get service url. * combobox functionality and test geonode connection * Add WFS. * Disable add button if it's a map. Currently we can't do anything for map. * Add busy cursor when add layer. * get service uri capabilitites * add available layers to the geonode browser panel * remove debugging footprint and replace old style connect * add actions (new, edit, delete) to geonode browser panel * fix getLayers by WMS url * add Geonode connection menu to the toolbar * Filter out invalid layer / map. * Fix service url method. * Add service url for XYZ for GeoNode QGIS Server backend. * Add XYZ url to geonode connection dialog. * Add XYZ layer to QGIS. * fix double geonode submenu * add wfs/wms layers from browser panel using its native provider * comply with qgis3 new class naming * Handle different prefix for layer in GeoNode QGIS Server backend. * base class for cms connection * make geonode connection as a derived class from cms connection * update cmakelists * move geonode connection to geocms dir * update CMakeLists * Handle geonode 2.7 with new API. * Handle multiple geoserver url in one geonode. * Fix add xyz for qgis server. Fix add wms, wfs, xyz for geoserver in geonode 2.7 * Refactor serviceURL to return QStringList. * add 'add geonode layer' icon * add geonode to the data source manager dialog * [GeoNode-Client] Fix add WFS layer. * fix wms url parameter * add xyz dataitems * Use new style connect. * [GeoNode Client] Handle qgis server specific typename to make add WFS works. * Code improvement. * [GeoNode Client] Make geonode dialog in add universal layer can add layer. * Open universal add layer when click Add GeoNode layer. * Make sure the geonode url has protocol. * Handle geonode version in a better way. * make sure the serviceUrl method has scheme in its urls * add services option to the dialog * remove version label if not wfs * construct wms url with parameters for geonode connection * handle layer from multi service urls for every wfs, wms, & xyz services * fix new style connect using static_cast * hode close button if dialog is in embedded mode * fix xyz layer naming in the browser tree * create base class for geocms dataitems * fix compiling warning * Use struct instead QVariantMap. * tidy up code * Tidy up code, use QgsStringMap instead QVariantMap. * Add spellok for catalogue. * update sip * update sip * Use naming convention for QgsGeoCmsConnection and use QUuid. * Async by using GeoNodeRequest class. * Move geonode to src/gui. * Use stack not heap. * Remove unused includes. * Use signal to handle request. * Use QStringLiteral. * Switch to use Q_FOREACH. * Use Q_FOREACH and addressing PR's review. * Set private for data members. * update sip * Update sip. * Update sip. * Fix sip problem to make it buildable again. * Remove geocms * Tidy up code. * Use QgsSetting Scope::Provider. * Fix missing zip.h --- .gitignore | 2 +- images/images.qrc | 2 + .../themes/default/mActionAddGeonodeLayer.svg | 407 ++++++++++++++ images/themes/default/mIconGeonode.svg | 13 + python/CMakeLists.txt | 2 + python/core/core_auto.sip | 2 + python/core/geonode/qgsdataitemprovider.sip | 0 python/core/geonode/qgsgeonodeconnection.sip | 86 +++ python/core/geonode/qgsgeonoderequest.sip | 121 +++++ python/core/qgsdataitemprovider.sip | 5 + .../gui/geonode/qgsgeonodenewconnection.sip | 51 ++ python/gui/geonode/qgsgeonodesourceselect.sip | 64 +++ python/gui/gui_auto.sip | 2 + python/gui/qgsmanageconnectionsdialog.sip | 1 + src/app/CMakeLists.txt | 2 + src/app/qgisapp.cpp | 31 +- src/app/qgisapp.h | 6 + src/app/qgisappinterface.cpp | 4 + src/app/qgisappinterface.h | 3 + src/core/CMakeLists.txt | 13 +- src/core/geonode/qgsgeonodeconnection.cpp | 117 ++++ src/core/geonode/qgsgeonodeconnection.h | 72 +++ src/core/geonode/qgsgeonoderequest.cpp | 420 +++++++++++++++ src/core/geonode/qgsgeonoderequest.h | 112 ++++ src/core/qgsdataitemprovider.h | 4 + src/gui/CMakeLists.txt | 12 + src/gui/geonode/qgsgeonodenewconnection.cpp | 266 +++++++++ src/gui/geonode/qgsgeonodenewconnection.h | 47 ++ src/gui/geonode/qgsgeonodesourceselect.cpp | 505 ++++++++++++++++++ src/gui/geonode/qgsgeonodesourceselect.h | 88 +++ src/gui/qgsdatasourcemanagerdialog.cpp | 18 + src/gui/qgsdatasourcemanagerdialog.h | 2 + src/gui/qgsmanageconnectionsdialog.cpp | 123 +++++ src/gui/qgsmanageconnectionsdialog.h | 3 + src/gui/qgsnewhttpconnection.cpp | 1 + src/providers/ows/CMakeLists.txt | 8 +- src/providers/ows/qgsgeonodedataitems.cpp | 273 ++++++++++ src/providers/ows/qgsgeonodedataitems.h | 87 +++ src/providers/ows/qgsowsdataitems.cpp | 21 +- src/providers/ows/qgsowsdataitems.h | 15 + src/providers/wfs/CMakeLists.txt | 1 + src/providers/wfs/qgswfsdataitems.cpp | 87 +++ src/providers/wfs/qgswfsdataitems.h | 16 + src/providers/wms/CMakeLists.txt | 1 + src/providers/wms/qgswmsdataitems.cpp | 89 +++ src/providers/wms/qgswmsdataitems.h | 5 + src/ui/qgsgeonodesourceselectbase.ui | 217 ++++++++ src/ui/qgsnewgeonodeconnectionbase.ui | 355 ++++++++++++ src/ui/qgsnewhttpconnectionbase.ui | 2 +- tests/src/core/CMakeLists.txt | 2 + tests/src/core/testqgsgeonodeconnection.cpp | 126 +++++ 51 files changed, 3894 insertions(+), 18 deletions(-) create mode 100644 images/themes/default/mActionAddGeonodeLayer.svg create mode 100644 images/themes/default/mIconGeonode.svg create mode 100644 python/core/geonode/qgsdataitemprovider.sip create mode 100644 python/core/geonode/qgsgeonodeconnection.sip create mode 100644 python/core/geonode/qgsgeonoderequest.sip create mode 100644 python/gui/geonode/qgsgeonodenewconnection.sip create mode 100644 python/gui/geonode/qgsgeonodesourceselect.sip create mode 100644 src/core/geonode/qgsgeonodeconnection.cpp create mode 100644 src/core/geonode/qgsgeonodeconnection.h create mode 100644 src/core/geonode/qgsgeonoderequest.cpp create mode 100644 src/core/geonode/qgsgeonoderequest.h create mode 100644 src/gui/geonode/qgsgeonodenewconnection.cpp create mode 100644 src/gui/geonode/qgsgeonodenewconnection.h create mode 100644 src/gui/geonode/qgsgeonodesourceselect.cpp create mode 100644 src/gui/geonode/qgsgeonodesourceselect.h create mode 100644 src/providers/ows/qgsgeonodedataitems.cpp create mode 100644 src/providers/ows/qgsgeonodedataitems.h create mode 100644 src/ui/qgsgeonodesourceselectbase.ui create mode 100644 src/ui/qgsnewgeonodeconnectionbase.ui create mode 100644 tests/src/core/testqgsgeonodeconnection.cpp diff --git a/.gitignore b/.gitignore index d31f8283e409..ff8aec0d3bb8 100644 --- a/.gitignore +++ b/.gitignore @@ -23,7 +23,7 @@ /CMakeLists.txt.user /CMakeLists.txt.user.* api_doc -build* +*build* debian/*.debhelper debian/*.substvars desktop.ini diff --git a/images/images.qrc b/images/images.qrc index f8baa88fd4ae..52e09ddc6b1e 100644 --- a/images/images.qrc +++ b/images/images.qrc @@ -135,6 +135,7 @@ themes/default/mActionAddWcsLayer.svg themes/default/mActionAddWfsLayer.svg themes/default/mActionAddWmsLayer.svg + themes/default/mActionAddGeonodeLayer.svg themes/default/mActionAddDelimitedTextLayer.svg themes/default/mActionAddVirtualLayer.svg themes/default/mActionAlignBottom.svg @@ -356,6 +357,7 @@ themes/default/mIconFieldInteger.svg themes/default/mIconFieldText.svg themes/default/mIconFieldTime.svg + themes/default/mIconGeonode.svg themes/default/mIconInfo.svg themes/default/mIconImport.gif themes/default/mIconLabelQuadrantCenter.svg diff --git a/images/themes/default/mActionAddGeonodeLayer.svg b/images/themes/default/mActionAddGeonodeLayer.svg new file mode 100644 index 000000000000..1b42b6c6115b --- /dev/null +++ b/images/themes/default/mActionAddGeonodeLayer.svg @@ -0,0 +1,407 @@ + + + + diff --git a/images/themes/default/mIconGeonode.svg b/images/themes/default/mIconGeonode.svg new file mode 100644 index 000000000000..e2c81d7654d2 --- /dev/null +++ b/images/themes/default/mIconGeonode.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index 6109cafd8233..a3e4c169e86a 100755 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -112,6 +112,7 @@ INCLUDE_DIRECTORIES( ${CMAKE_SOURCE_DIR}/src/core/fieldformatter ${CMAKE_SOURCE_DIR}/src/core/dxf ${CMAKE_SOURCE_DIR}/src/core/geometry + ${CMAKE_SOURCE_DIR}/src/core/geonode ${CMAKE_SOURCE_DIR}/src/core/gps ${CMAKE_SOURCE_DIR}/src/core/layertree ${CMAKE_SOURCE_DIR}/src/core/layout @@ -141,6 +142,7 @@ IF (WITH_GUI) ${CMAKE_SOURCE_DIR}/src/gui/editorwidgets ${CMAKE_SOURCE_DIR}/src/gui/editorwidgets/core ${CMAKE_SOURCE_DIR}/src/gui/effects + ${CMAKE_SOURCE_DIR}/src/gui/geonode ${CMAKE_SOURCE_DIR}/src/gui/layertree ${CMAKE_SOURCE_DIR}/src/gui/layout ${CMAKE_SOURCE_DIR}/src/gui/locator diff --git a/python/core/core_auto.sip b/python/core/core_auto.sip index e91b847b5762..4ad20561b89a 100644 --- a/python/core/core_auto.sip +++ b/python/core/core_auto.sip @@ -280,6 +280,8 @@ %Include fieldformatter/qgsrelationreferencefieldformatter.sip %Include fieldformatter/qgsvaluemapfieldformatter.sip %Include fieldformatter/qgsvaluerelationfieldformatter.sip +%Include geonode/qgsgeonodeconnection.sip +%Include geonode/qgsgeonoderequest.sip %Include gps/qgsqtlocationconnection.sip %Include gps/qgsgpsconnectionregistry.sip %Include qgsapplication.sip diff --git a/python/core/geonode/qgsdataitemprovider.sip b/python/core/geonode/qgsdataitemprovider.sip new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/core/geonode/qgsgeonodeconnection.sip b/python/core/geonode/qgsgeonodeconnection.sip new file mode 100644 index 000000000000..62010a85c850 --- /dev/null +++ b/python/core/geonode/qgsgeonodeconnection.sip @@ -0,0 +1,86 @@ +/************************************************************************ + * This file has been generated automatically from * + * * + * src/core/geonode/qgsgeonodeconnection.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ + + + + +class QgsGeoNodeConnection : QObject +{ +%Docstring +! + GeoNode Connections management +%End + +%TypeHeaderCode +#include "qgsgeonodeconnection.h" +%End + public: + explicit QgsGeoNodeConnection( const QString &connName ); +%Docstring +Constructor +%End + + ~QgsGeoNodeConnection(); +%Docstring +Destructor +%End + + QString connName() const; +%Docstring + :rtype: str +%End + void setConnName( const QString &connName ); + + QgsDataSourceUri uri(); +%Docstring + :rtype: QgsDataSourceUri +%End + void setUri( const QgsDataSourceUri &uri ); + + static QStringList connectionList(); +%Docstring +Retrieve all geonode connection + :rtype: list of str +%End + + static void deleteConnection( const QString &name ); +%Docstring +Delete connection with name, name +%End + + static QString selectedConnection(); +%Docstring +Get selected connection + :rtype: str +%End + + static void setSelectedConnection( const QString &name ); +%Docstring +Set selected connection +%End + + static QString pathGeoNodeConnection(); +%Docstring + :rtype: str +%End + + static QString pathGeoNodeConnectionDetails(); +%Docstring + :rtype: str +%End + +}; + + +/************************************************************************ + * This file has been generated automatically from * + * * + * src/core/geonode/qgsgeonodeconnection.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ diff --git a/python/core/geonode/qgsgeonoderequest.sip b/python/core/geonode/qgsgeonoderequest.sip new file mode 100644 index 000000000000..21cf76b008ac --- /dev/null +++ b/python/core/geonode/qgsgeonoderequest.sip @@ -0,0 +1,121 @@ +/************************************************************************ + * This file has been generated automatically from * + * * + * src/core/geonode/qgsgeonoderequest.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ + + + + +struct QgsServiceLayerDetail +{ +%TypeHeaderCode +#include +%End + QUuid uuid; + QString name; + QString typeName; + QString title; + QString wmsURL; + QString wfsURL; + QString xyzURL; +}; + +class QgsGeoNodeRequest : QObject +{ + +%TypeHeaderCode +#include "qgsgeonoderequest.h" +%End + public: + explicit QgsGeoNodeRequest( bool forceRefresh, QObject *parent = 0 ); + QgsGeoNodeRequest( const QString &baseUrl, /*const QgsWmsAuthorization &auth,*/ bool forceRefresh, QObject *parent = 0 ); + virtual ~QgsGeoNodeRequest(); + + bool request( QString endPoint ); +%Docstring + :rtype: bool +%End + + bool getLayers(); +%Docstring + :rtype: bool +%End + + QList parseLayers( QByteArray layerResponse ); +%Docstring + :rtype: list of QgsServiceLayerDetail +%End + + QStringList serviceUrls( QString serviceType ); +%Docstring + :rtype: list of str +%End + + QgsStringMap serviceUrlData( QString serviceType ); +%Docstring + :rtype: QgsStringMap +%End + + QString lastError() const; +%Docstring + :rtype: str +%End + + QByteArray response() const; +%Docstring + :rtype: QByteArray +%End + + QNetworkReply *reply() const; +%Docstring + :rtype: QNetworkReply +%End + + void abort(); +%Docstring +Abort network request immediately +%End + + QString getProtocol() const; +%Docstring + :rtype: str +%End + void setProtocol( const QString &protocol ); + + signals: + void statusChanged( const QString &statusQString ); +%Docstring + emit a signal to be caught by qgisapp and display a statusQString on status bar +%End + + void requestFinished(); +%Docstring + emit a signal once the request is finished +%End + + protected slots: + void replyFinished(); + void replyProgress( qint64, qint64 ); + + protected: + + + + + + + + + +}; + +/************************************************************************ + * This file has been generated automatically from * + * * + * src/core/geonode/qgsgeonoderequest.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ diff --git a/python/core/qgsdataitemprovider.sip b/python/core/qgsdataitemprovider.sip index f62452c2ffe7..69aa1157ce2a 100644 --- a/python/core/qgsdataitemprovider.sip +++ b/python/core/qgsdataitemprovider.sip @@ -49,6 +49,11 @@ Caller takes responsibility of deleting created items. :rtype: QgsDataItem %End + virtual QVector createDataItems( const QString &path, QgsDataItem *parentItem ); +%Docstring +Caller takes responsibility of deleting created items. + :rtype: list of QgsDataItem +%End }; /************************************************************************ diff --git a/python/gui/geonode/qgsgeonodenewconnection.sip b/python/gui/geonode/qgsgeonodenewconnection.sip new file mode 100644 index 000000000000..2f8ba33ebdf7 --- /dev/null +++ b/python/gui/geonode/qgsgeonodenewconnection.sip @@ -0,0 +1,51 @@ +/************************************************************************ + * This file has been generated automatically from * + * * + * src/gui/geonode/qgsgeonodenewconnection.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ + + + + +class QgsGeoNodeNewConnection : QDialog +{ +%Docstring +************************************************************************* + * + 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. * + * +************************************************************************** +%End + +%TypeHeaderCode +#include "qgsgeonodenewconnection.h" +%End + public: + QgsGeoNodeNewConnection( QWidget *parent = 0, const QString &connName = QString::null, Qt::WindowFlags fl = QgsGuiUtils::ModalDialogFlags ); +%Docstring +Constructor +%End + + public slots: + virtual void accept(); + + void okButtonBehavior( const QString & ); + void testConnection(); +%Docstring +Test the connection using the parameters supplied +%End + +}; + +/************************************************************************ + * This file has been generated automatically from * + * * + * src/gui/geonode/qgsgeonodenewconnection.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ diff --git a/python/gui/geonode/qgsgeonodesourceselect.sip b/python/gui/geonode/qgsgeonodesourceselect.sip new file mode 100644 index 000000000000..dc27ff92c511 --- /dev/null +++ b/python/gui/geonode/qgsgeonodesourceselect.sip @@ -0,0 +1,64 @@ +/************************************************************************ + * This file has been generated automatically from * + * * + * src/gui/geonode/qgsgeonodesourceselect.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ + + + + +class QgsGeonodeItemDelegate : QItemDelegate +{ +%Docstring +************************************************************************* + * + 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. * + * +************************************************************************** +%End + +%TypeHeaderCode +#include "qgsgeonodesourceselect.h" +%End + public: + explicit QgsGeonodeItemDelegate( QObject *parent = 0 ); +}; + +class QgsGeoNodeSourceSelect: QDialog +{ + +%TypeHeaderCode +#include "qgsgeonodesourceselect.h" +%End + public: + + QgsGeoNodeSourceSelect( QWidget *parent, Qt::WindowFlags fl, bool embeddedMode = false ); + ~QgsGeoNodeSourceSelect(); + + signals: + void connectionsChanged(); + void addRasterLayer( const QString &rasterLayerPath, + const QString &baseName, + const QString &providerKey ); + void addRasterLayer(); + + void addWfsLayer( + const QString &uri, + const QString &layerName, + const QString &providerKey ); + +}; + + +/************************************************************************ + * This file has been generated automatically from * + * * + * src/gui/geonode/qgsgeonodesourceselect.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ diff --git a/python/gui/gui_auto.sip b/python/gui/gui_auto.sip index 071eb2153b12..4254fec54495 100644 --- a/python/gui/gui_auto.sip +++ b/python/gui/gui_auto.sip @@ -40,6 +40,8 @@ %Include auth/qgsauthtrustedcasdialog.sip %Include editorwidgets/core/qgseditorwidgetfactory.sip %Include editorwidgets/core/qgseditorwidgetautoconf.sip +%Include geonode/qgsgeonodenewconnection.sip +%Include geonode/qgsgeonodesourceselect.sip %Include layertree/qgslayertreeembeddedconfigwidget.sip %Include layertree/qgslayertreeembeddedwidgetregistry.sip %Include layout/qgslayoutviewmouseevent.sip diff --git a/python/gui/qgsmanageconnectionsdialog.sip b/python/gui/qgsmanageconnectionsdialog.sip index 6b0c695f3677..0c7114c31e30 100644 --- a/python/gui/qgsmanageconnectionsdialog.sip +++ b/python/gui/qgsmanageconnectionsdialog.sip @@ -31,6 +31,7 @@ class QgsManageConnectionsDialog : QDialog DB2, WCS, Oracle, + GeoNode }; QgsManageConnectionsDialog( QWidget *parent /TransferThis/ = 0, Mode mode = Export, Type type = WMS, const QString &fileName = "" ); diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt index 7f7838624c70..3b969ab8763e 100755 --- a/src/app/CMakeLists.txt +++ b/src/app/CMakeLists.txt @@ -510,6 +510,7 @@ INCLUDE_DIRECTORIES( ${CMAKE_SOURCE_DIR}/src/gui/symbology ${CMAKE_SOURCE_DIR}/src/gui/attributetable ${CMAKE_SOURCE_DIR}/src/gui/auth + ${CMAKE_SOURCE_DIR}/src/gui/geonode ${CMAKE_SOURCE_DIR}/src/gui/ogr ${CMAKE_SOURCE_DIR}/src/gui/raster ${CMAKE_SOURCE_DIR}/src/gui/editorwidgets @@ -556,6 +557,7 @@ INCLUDE_DIRECTORIES( ../gui/symbology ../gui/attributetable ../gui/auth + ../gui/geonode ../gui/ogr ../gui/raster ../gui/editorwidgets diff --git a/src/app/qgisapp.cpp b/src/app/qgisapp.cpp index 8ff143c7e2fb..7a68b8148c88 100644 --- a/src/app/qgisapp.cpp +++ b/src/app/qgisapp.cpp @@ -294,6 +294,7 @@ Q_GUI_EXPORT extern int qt_defaultDpiX(); #include "qgsuserprofile.h" #include "qgssublayersdialog.h" +#include "geonode/qgsgeonodesourceselect.h" #include "ogr/qgsvectorlayersaveasdialog.h" #include "qgsosmdownloaddialog.h" @@ -811,9 +812,9 @@ QgisApp::QgisApp( QSplashScreen *splash, bool restorePlugins, bool skipVersionCh endProfile(); + functionProfile( &QgisApp::createMenus, this, QStringLiteral( "Create menus" ) ); functionProfile( &QgisApp::createActions, this, QStringLiteral( "Create actions" ) ); functionProfile( &QgisApp::createActionGroups, this, QStringLiteral( "Create action group" ) ); - functionProfile( &QgisApp::createMenus, this, QStringLiteral( "Create menus" ) ); functionProfile( &QgisApp::createToolBars, this, QStringLiteral( "Toolbars" ) ); functionProfile( &QgisApp::createStatusBar, this, QStringLiteral( "Status bar" ) ); functionProfile( &QgisApp::createCanvasTools, this, QStringLiteral( "Create canvas tools" ) ); @@ -1049,6 +1050,11 @@ QgisApp::QgisApp( QSplashScreen *splash, bool restorePlugins, bool skipVersionCh mActionInstallFromZip = nullptr; } + // add geonode menu under web menu + addPluginToWebMenu( mGeonodeMenu->title(), mActionAddGeonodeLayer ); + mActionAddGeonodeLayer = new QAction( tr( "Add GeoNode Layers..." ), this ); + mGeonodeMenu->addAction( mActionAddGeonodeLayer ); + // Set icon size of toolbars if ( settings.contains( QStringLiteral( "IconSize" ) ) ) { @@ -1904,6 +1910,8 @@ void QgisApp::createActions() connect( mActionLabeling, &QAction::triggered, this, &QgisApp::labeling ); connect( mActionStatisticalSummary, &QAction::triggered, this, &QgisApp::showStatisticsDockWidget ); + // Web Menu Items + // Layer Menu Items connect( mActionDataSourceManager, &QAction::triggered, this, [ = ]() { dataSourceManager(); } ); @@ -1929,6 +1937,7 @@ void QgisApp::createActions() connect( mActionAddAmsLayer, &QAction::triggered, this, [ = ] { dataSourceManager( QStringLiteral( "arcgismapserver" ) ); } ); connect( mActionAddDelimitedText, &QAction::triggered, this, [ = ] { dataSourceManager( QStringLiteral( "delimitedtext" ) ); } ); connect( mActionAddVirtualLayer, &QAction::triggered, this, [ = ] { dataSourceManager( QStringLiteral( "virtual" ) ); } ); + connect( mActionAddGeonodeLayer, &QAction::triggered, this, [ = ] { dataSourceManager( QStringLiteral( "geonode" ) ); } ); connect( mActionOpenTable, &QAction::triggered, this, &QgisApp::attributeTable ); connect( mActionOpenFieldCalc, &QAction::triggered, this, &QgisApp::fieldCalculator ); connect( mActionToggleEditing, &QAction::triggered, this, [ = ] { toggleEditing(); } ); @@ -2204,6 +2213,12 @@ void QgisApp::createMenus() mToolbarMenu = new QMenu( tr( "Toolbars" ), this ); mToolbarMenu->setObjectName( QStringLiteral( "mToolbarMenu" ) ); + // Geonode Submenu + mGeonodeMenu = new QMenu( tr( "GeoNode" ), this ); + mGeonodeMenu->setObjectName( QStringLiteral( "mGeonodeMenu" ) ); + // Geonode Action + mActionAddGeonodeLayer = new QAction( tr( "Add GeoNode Layers..." ), this ); + // Get platform for menu layout customization (Gnome, Kde, Mac, Win) QDialogButtonBox::ButtonLayout layout = QDialogButtonBox::ButtonLayout( style()->styleHint( QStyle::SH_DialogButtonLayout, nullptr, this ) ); @@ -4556,6 +4571,20 @@ void QgisApp::askUserForOGRSublayers( QgsVectorLayer *layer ) } } +void QgisApp::addGeonodeLayer() +{ + QgsGeoNodeSourceSelect *geonodes = new QgsGeoNodeSourceSelect( this, 0, true ); + if ( !geonodes ) + { + QMessageBox::warning( this, tr( "Geonode" ), tr( "Cannot get Geonode select dialog." ) ); + return; + } + connect( geonodes, static_cast( &QgsGeoNodeSourceSelect::addRasterLayer ), this, static_cast( &QgisApp::addRasterLayer ) ); + connect( geonodes, &QgsGeoNodeSourceSelect::addWfsLayer, this, &QgisApp::addVectorLayer ); + geonodes->exec(); + delete geonodes; +} + void QgisApp::addDatabaseLayer() { #ifdef HAVE_POSTGRESQL diff --git a/src/app/qgisapp.h b/src/app/qgisapp.h index 38f7138114e2..b4c7f91f241a 100644 --- a/src/app/qgisapp.h +++ b/src/app/qgisapp.h @@ -437,6 +437,8 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow QAction *actionShowBookmarks() { return mActionShowBookmarks; } QAction *actionDraw() { return mActionDraw; } + QAction *actionAddGeonodeLayer() { return mActionAddGeonodeLayer; } + QAction *actionDataSourceManager() { return mActionDataSourceManager; } QAction *actionNewVectorLayer() { return mActionNewVectorLayer; } QAction *actionNewSpatialLiteLayer() { return mActionNewSpatiaLiteLayer; } @@ -885,6 +887,8 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow void sponsors(); //! About QGIS void about(); + //! Add a Geonode layer to the map + void addGeonodeLayer(); //#ifdef HAVE_POSTGRESQL //! Add a databaselayer to the map void addDatabaseLayer(); @@ -1731,6 +1735,7 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow QAction *mActionPluginSeparator1 = nullptr; QAction *mActionPluginSeparator2 = nullptr; QAction *mActionRasterSeparator = nullptr; + QAction *mActionAddGeonodeLayer = nullptr; // action groups ---------------------------------- QActionGroup *mMapToolGroup = nullptr; @@ -1743,6 +1748,7 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow #endif QMenu *mPanelMenu = nullptr; QMenu *mToolbarMenu = nullptr; + QMenu *mGeonodeMenu = nullptr; // docks ------------------------------------------ QgsDockWidget *mLayerTreeDock = nullptr; diff --git a/src/app/qgisappinterface.cpp b/src/app/qgisappinterface.cpp index 8582c468fe28..e510148348f9 100644 --- a/src/app/qgisappinterface.cpp +++ b/src/app/qgisappinterface.cpp @@ -648,6 +648,10 @@ QAction *QgisAppInterface::actionNewBookmark() { return qgis->actionNewBookmark( QAction *QgisAppInterface::actionShowBookmarks() { return qgis->actionShowBookmarks(); } QAction *QgisAppInterface::actionDraw() { return qgis->actionDraw(); } +//! Web menu actions +QAction *QgisAppInterface::actionAddGeonodeLayer() { return qgis->actionAddGeonodeLayer(); } + +//! Layer menu actions QAction *QgisAppInterface::actionNewVectorLayer() { return qgis->actionNewVectorLayer(); } QAction *QgisAppInterface::actionAddOgrLayer() { return qgis->actionAddOgrLayer(); } QAction *QgisAppInterface::actionAddRasterLayer() { return qgis->actionAddRasterLayer(); } diff --git a/src/app/qgisappinterface.h b/src/app/qgisappinterface.h index d659533d50c8..0db1f28937a2 100644 --- a/src/app/qgisappinterface.h +++ b/src/app/qgisappinterface.h @@ -396,6 +396,9 @@ class APP_EXPORT QgisAppInterface : public QgisInterface virtual QAction *actionShowBookmarks() override; virtual QAction *actionDraw() override; + //! Web menu actions + virtual QAction *actionAddGeonodeLayer(); + //! Layer menu actions virtual QAction *actionNewVectorLayer() override; virtual QAction *actionAddOgrLayer() override; diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 7f8db89fdceb..2159a442ce75 100755 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -450,7 +450,6 @@ SET(QGIS_CORE_SRCS geometry/qgswkbptr.cpp geometry/qgswkbtypes.cpp - fieldformatter/qgsdatetimefieldformatter.cpp fieldformatter/qgsfallbackfieldformatter.cpp fieldformatter/qgskeyvaluefieldformatter.cpp @@ -459,6 +458,9 @@ SET(QGIS_CORE_SRCS fieldformatter/qgsvaluemapfieldformatter.cpp fieldformatter/qgsvaluerelationfieldformatter.cpp + geonode/qgsgeonodeconnection.cpp + geonode/qgsgeonoderequest.cpp + ${CMAKE_CURRENT_BINARY_DIR}/qgscontexthelp_texts.cpp ${CMAKE_CURRENT_BINARY_DIR}/qgsexpression_texts.cpp @@ -701,7 +703,11 @@ SET(QGIS_CORE_MOC_HDRS layertree/qgslayertreemodellegendnode.h layertree/qgslayertreenode.h layertree/qgslayertreeregistrybridge.h + qgsuserprofilemanager.h + + geonode/qgsgeonodeconnection.h + geonode/qgsgeonoderequest.h ) IF (NOT WITH_QTWEBKIT) @@ -1075,6 +1081,9 @@ SET(QGIS_CORE_HDRS fieldformatter/qgsvaluemapfieldformatter.h fieldformatter/qgsvaluerelationfieldformatter.h + geonode/qgsgeonodeconnection.h + geonode/qgsgeonoderequest.h + metadata/qgslayermetadata.h metadata/qgslayermetadatavalidator.h ) @@ -1089,6 +1098,7 @@ ENDIF (QT_MOBILITY_LOCATION_FOUND OR Qt5Positioning_FOUND) INCLUDE_DIRECTORIES( ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} + ${LIBZIP_INCLUDE_DIR} annotations auth composer @@ -1097,6 +1107,7 @@ INCLUDE_DIRECTORIES( ${CMAKE_SOURCE_DIR}/src/core/expression fieldformatter geometry + geonode layertree layout metadata diff --git a/src/core/geonode/qgsgeonodeconnection.cpp b/src/core/geonode/qgsgeonodeconnection.cpp new file mode 100644 index 000000000000..f92decde9a26 --- /dev/null +++ b/src/core/geonode/qgsgeonodeconnection.cpp @@ -0,0 +1,117 @@ +/*************************************************************************** + qgsgeonodeconnection.cpp + --------------------- + begin : Feb 2017 + copyright : (C) 2017 by Muhammad Yarjuna Rohmat, Ismail Sunni + email : rohmat at kartoza dot com, ismail at kartoza 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 "qgssettings.h" +#include "qgsgeonodeconnection.h" +#include "qgslogger.h" +#include "qgsdatasourceuri.h" + +const QString QgsGeoNodeConnection::mPathGeoNodeConnection = "qgis/connections-geonode"; +const QString QgsGeoNodeConnection::mPathGeoNodeConnectionDetails = "qgis/GeoNode"; + +QgsGeoNodeConnection::QgsGeoNodeConnection( const QString &connName ) + : mConnName( connName ) +{ + QgsSettings settings; + + +// settings.Section + QString key = "qgis/connections-geonode/" + mConnName; + QString credentialsKey = "qgis/geonode/" + mConnName; + + QStringList connStringParts; + + mUri.setParam( QStringLiteral( "url" ), settings.value( key + "/url", "", QgsSettings::Providers ).toString() ); + + // Check for credentials and prepend to the connection info + QString username = settings.value( credentialsKey + "/username", "", QgsSettings::Providers ).toString(); + QString password = settings.value( credentialsKey + "/password", "", QgsSettings::Providers ).toString(); + if ( !username.isEmpty() ) + { + mUri.setParam( QStringLiteral( "username" ), username ); + mUri.setParam( QStringLiteral( "password" ), password ); + } + + QString authcfg = settings.value( credentialsKey + "/authcfg", "", QgsSettings::Providers ).toString(); + if ( !authcfg.isEmpty() ) + { + mUri.setParam( QStringLiteral( "authcfg" ), authcfg ); + } + + QgsDebugMsg( QString( "encodedUri: '%1'." ).arg( QString( mUri.encodedUri() ) ) ); +} + +QgsGeoNodeConnection::~QgsGeoNodeConnection() +{ + +} + +QgsDataSourceUri QgsGeoNodeConnection::uri() +{ + return mUri; +} + +QStringList QgsGeoNodeConnection::connectionList() +{ + QgsSettings settings; + // Add Section manually + settings.beginGroup( "providers/qgis/connections-geonode" ); + return settings.childGroups(); +} + +void QgsGeoNodeConnection::deleteConnection( const QString &name ) +{ + QgsSettings settings; + // Add Section manually + settings.remove( "providers/qgis/connections-geonode/" + name ); + settings.remove( "providers/qgis/geonode/" + name ); +} + +QString QgsGeoNodeConnection::selectedConnection() +{ + QgsSettings settings; + return settings.value( "qgis/connections-geonode/selected", "", QgsSettings::Providers ).toString(); +} + +void QgsGeoNodeConnection::setSelectedConnection( const QString &name ) +{ + QgsSettings settings; + settings.setValue( "qgis/connections-geonode/selected", name, QgsSettings::Providers ); +} + +QString QgsGeoNodeConnection::pathGeoNodeConnection() +{ + return mPathGeoNodeConnection; +} + +QString QgsGeoNodeConnection::pathGeoNodeConnectionDetails() +{ + return mPathGeoNodeConnectionDetails; +} + +QString QgsGeoNodeConnection::connName() const +{ + return mConnName; +} + +void QgsGeoNodeConnection::setConnName( const QString &connName ) +{ + mConnName = connName; +} + +void QgsGeoNodeConnection::setUri( const QgsDataSourceUri &uri ) +{ + mUri = uri; +} diff --git a/src/core/geonode/qgsgeonodeconnection.h b/src/core/geonode/qgsgeonodeconnection.h new file mode 100644 index 000000000000..67f05478a2c8 --- /dev/null +++ b/src/core/geonode/qgsgeonodeconnection.h @@ -0,0 +1,72 @@ +/*************************************************************************** + qgsgeonodeconnection.h + --------------------- + begin : Feb 2017 + copyright : (C) 2017 by Muhammad Yarjuna Rohmat, Ismail Sunni + email : rohmat at kartoza dot com, ismail at kartoza 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. * + * * + ***************************************************************************/ + +#ifndef QGSGEONODECONNECTION_H +#define QGSGEONODECONNECTION_H + +#include "qgis_core.h" +#include "qgsdatasourceuri.h" + + +/*! + * \brief GeoNode Connections management + */ +class CORE_EXPORT QgsGeoNodeConnection : public QObject +{ + Q_OBJECT + + public: + //! Constructor + explicit QgsGeoNodeConnection( const QString &connName ); + + //! Destructor + ~QgsGeoNodeConnection(); + + QString connName() const; + void setConnName( const QString &connName ); + + QgsDataSourceUri uri(); + void setUri( const QgsDataSourceUri &uri ); + + //! Retrieve all geonode connection + static QStringList connectionList(); + + //! Delete connection with name, name + static void deleteConnection( const QString &name ); + + //! Get selected connection + static QString selectedConnection(); + + //! Set selected connection + static void setSelectedConnection( const QString &name ); + + static QString pathGeoNodeConnection(); + + static QString pathGeoNodeConnectionDetails(); + + private: + // Path in QSetting + static const QString mPathGeoNodeConnection; + static const QString mPathGeoNodeConnectionDetails; + + //! The connection name + QString mConnName; + + //! Property of mUri + QgsDataSourceUri mUri; +}; + + +#endif //QGSGEONODECONNECTION_H diff --git a/src/core/geonode/qgsgeonoderequest.cpp b/src/core/geonode/qgsgeonoderequest.cpp new file mode 100644 index 000000000000..2ecc83278105 --- /dev/null +++ b/src/core/geonode/qgsgeonoderequest.cpp @@ -0,0 +1,420 @@ +/*************************************************************************** + qgsgeonoderequest.h + --------------------- + begin : Jul 2017 + copyright : (C) 2017 by Muhammad Yarjuna Rohmat, Ismail Sunni + email : rohmat at kartoza dot com, ismail at kartoza 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 "qgsnetworkaccessmanager.h" +#include "qgssettings.h" +#include "qgsmessagelog.h" +#include "qgslogger.h" +#include "qgsgeonoderequest.h" + +#include +#include +#include +#include +#include +#include +#include + +QgsGeoNodeRequest::QgsGeoNodeRequest( bool forceRefresh, QObject *parent ) + : QObject( parent ) + , mForceRefresh( forceRefresh ) +{ + +} + +QgsGeoNodeRequest::QgsGeoNodeRequest( const QString &baseUrl, /*const QgsWmsAuthorization &auth,*/ bool forceRefresh, QObject *parent ) + : QObject( parent ) + , mBaseUrl( baseUrl ) + , mGeoNodeReply( nullptr ) + , mIsAborted( false ) + , mForceRefresh( forceRefresh ) +{ + +} + +QgsGeoNodeRequest::~QgsGeoNodeRequest() +{ + abort(); +} + +void QgsGeoNodeRequest::abort() +{ + mIsAborted = true; + if ( mGeoNodeReply ) + { + mGeoNodeReply->deleteLater(); + mGeoNodeReply = nullptr; + } +} + +bool QgsGeoNodeRequest::getLayers() +{ + return request( QStringLiteral( "/api/layers/" ) ); +} + +void QgsGeoNodeRequest::replyProgress( qint64 bytesReceived, qint64 bytesTotal ) +{ + QString msg = tr( "%1 of %2 bytes of request downloaded." ).arg( bytesReceived ).arg( bytesTotal < 0 ? QStringLiteral( "unknown number of" ) : QString::number( bytesTotal ) ); + QgsDebugMsg( msg ); + QgsMessageLog::logMessage( QStringLiteral( "Reply in progress" ), tr( "GeoNode" ) ); + QgsMessageLog::logMessage( msg, tr( "GeoNode" ) ); + emit statusChanged( msg ); +} + +QString QgsGeoNodeRequest::getProtocol() const +{ + return mProtocol; +} + +void QgsGeoNodeRequest::setProtocol( const QString &protocol ) +{ + mProtocol = protocol; +} + + +void QgsGeoNodeRequest::replyFinished() +{ + QgsMessageLog::logMessage( QStringLiteral( "Reply finished" ), tr( "GeoNode" ) ); + if ( !mIsAborted && mGeoNodeReply ) + { + if ( mGeoNodeReply->error() == QNetworkReply::NoError ) + { + QgsDebugMsg( "reply OK" ); + QVariant redirect = mGeoNodeReply->attribute( QNetworkRequest::RedirectionTargetAttribute ); + if ( !redirect.isNull() ) + { + + emit statusChanged( QStringLiteral( "GeoNode request redirected." ) ); + + const QUrl &toUrl = redirect.toUrl(); + mGeoNodeReply->request(); + if ( toUrl == mGeoNodeReply->url() ) + { + mError = tr( "Redirect loop detected: %1" ).arg( toUrl.toString() ); + QgsMessageLog::logMessage( mError, tr( "GeoNode" ) ); + mHttpGeoNodeResponse.clear(); + } + else + { + QNetworkRequest request( toUrl ); + + request.setAttribute( QNetworkRequest::CacheLoadControlAttribute, mForceRefresh ? QNetworkRequest::AlwaysNetwork : QNetworkRequest::PreferCache ); + request.setAttribute( QNetworkRequest::CacheSaveControlAttribute, true ); + + mGeoNodeReply->deleteLater(); + mGeoNodeReply = nullptr; + + QgsDebugMsg( QString( "redirected getcapabilities: %1 forceRefresh=%2" ).arg( redirect.toString() ).arg( mForceRefresh ) ); + mGeoNodeReply = QgsNetworkAccessManager::instance()->get( request ); + + connect( mGeoNodeReply, &QNetworkReply::finished, this, &QgsGeoNodeRequest::replyFinished, Qt::DirectConnection ); + connect( mGeoNodeReply, &QNetworkReply::downloadProgress, this, &QgsGeoNodeRequest::replyProgress, Qt::DirectConnection ); + return; + } + } + else + { + const QgsNetworkAccessManager *nam = QgsNetworkAccessManager::instance(); + + if ( nam->cache() ) + { + QNetworkCacheMetaData cmd = nam->cache()->metaData( mGeoNodeReply->request().url() ); + + QNetworkCacheMetaData::RawHeaderList hl; + Q_FOREACH ( const QNetworkCacheMetaData::RawHeader &h, cmd.rawHeaders() ) + { + if ( h.first != "Cache-Control" ) + hl.append( h ); + } + cmd.setRawHeaders( hl ); + + QgsDebugMsg( QString( "expirationDate:%1" ).arg( cmd.expirationDate().toString() ) ); + if ( cmd.expirationDate().isNull() ) + { + QgsSettings settings; + cmd.setExpirationDate( QDateTime::currentDateTime().addSecs( settings.value( QStringLiteral( "qgis/defaultCapabilitiesExpiry" ), "24", QgsSettings::Providers ).toInt() * 60 * 60 ) ); + } + + nam->cache()->updateMetaData( cmd ); + } + else + { + QgsDebugMsg( "No cache for capabilities!" ); + } + + mHttpGeoNodeResponse = mGeoNodeReply->readAll(); + + if ( mHttpGeoNodeResponse.isEmpty() ) + { + mError = tr( "empty of capabilities: %1" ).arg( mGeoNodeReply->errorString() ); + } + } + } + else + { + mError = tr( "Request failed: %1" ).arg( mGeoNodeReply->errorString() ); + QgsMessageLog::logMessage( mError, tr( "GeoNode" ) ); + mHttpGeoNodeResponse.clear(); + } + } + + if ( mGeoNodeReply ) + { + mGeoNodeReply->deleteLater(); + mGeoNodeReply = nullptr; + } + + emit requestFinished(); + +} + +QList QgsGeoNodeRequest::parseLayers( QByteArray layerResponse ) +{ + QList layers; + if ( layerResponse.isEmpty() ) + { + return layers; + } + + QJsonDocument jsonDocument = QJsonDocument::fromJson( layerResponse ); + QJsonObject jsonObject = jsonDocument.object(); + QVariantMap jsonVariantMap = jsonObject.toVariantMap(); + QVariantList layerList = jsonVariantMap["objects"].toList(); + qint16 majorVersion; + qint16 minorVersion; + if ( jsonVariantMap.contains( QStringLiteral( "geonode_version" ) ) ) + { + QStringList geonodeVersionSplit = jsonVariantMap["geonode_version"].toString().split( "." ); + majorVersion = geonodeVersionSplit[0].toInt(); + minorVersion = geonodeVersionSplit[1].toInt(); + } + else + { + majorVersion = 2; + minorVersion = 6; + } + + if ( majorVersion == 2 && minorVersion == 6 ) + { + Q_FOREACH ( QVariant layer, layerList ) + { + QgsServiceLayerDetail layerStruct; + QVariantMap layerMap = layer.toMap(); + // Find WMS and WFS. XYZ is not available + // Trick to get layer's typename from distribution_url or detail_url + QString layerTypeName = layerMap["detail_url"].toString().split( "/" ).last(); + if ( layerTypeName.length() == 0 ) + { + layerTypeName = layerMap["distribution_url"].toString().split( "/" ).last(); + } + // On this step, layerTypeName is in WORKSPACE%3ALAYERNAME or WORKSPACE:LAYERNAME format + if ( layerTypeName.contains( "%3A" ) ) + { + layerTypeName.replace( "%3A", ":" ); + } + // On this step, layerTypeName is in WORKSPACE:LAYERNAME format + QStringList splitURL = layerTypeName.split( ":" ); + QString layerWorkspace = splitURL[0]; + QString layerName = splitURL[1]; + + layerStruct.name = layerName; + layerStruct.typeName = layerTypeName; + layerStruct.uuid = layerMap["uuid"].toString(); + layerStruct.title = layerMap["title"].toString();; + + // WMS url : BASE_URI/geoserver/WORKSPACE/wms + layerStruct.wmsURL = mBaseUrl + "/geoserver/" + layerWorkspace + "/wms"; + // WFS url : BASE_URI/geoserver/WORKSPACE/wfs + layerStruct.wfsURL = mBaseUrl + "/geoserver/" + layerWorkspace + "/wfs"; + // XYZ url : set to empty string + layerStruct.xyzURL = ""; + + layers.append( layerStruct ); + } + } + // Geonode version 2.7 or newer + else if ( ( majorVersion == 2 && minorVersion >= 7 ) || ( majorVersion >= 3 ) ) + { + Q_FOREACH ( QVariant layer, layerList ) + { + QgsServiceLayerDetail layerStruct; + QVariantMap layerMap = layer.toMap(); + // Find WMS, WFS, and XYZ link + QVariantList layerLinks = layerMap["links"].toList(); + layerStruct.wmsURL = QStringLiteral( "" ); + layerStruct.wfsURL = QStringLiteral( "" ); + layerStruct.xyzURL = QStringLiteral( "" ); + Q_FOREACH ( QVariant link, layerLinks ) + { + QVariantMap linkMap = link.toMap(); + if ( linkMap.contains( "link_type" ) ) + { + if ( linkMap["link_type"] == "OGC:WMS" ) + { + layerStruct.wmsURL = linkMap["url"].toString(); + } + if ( linkMap["link_type"] == "OGC:WFS" ) + { + layerStruct.wfsURL = linkMap["url"].toString(); + } + if ( linkMap["link_type"] == "image" ) + { + if ( linkMap.contains( "name" ) && linkMap["name"] == "Tiles" ) + { + layerStruct.xyzURL = linkMap["url"].toString(); + } + } + } + } + if ( layerMap["typename"].toString().length() == 0 ) + { + QStringList splitURL = layerMap["detail_url"].toString().split( "/" ); + layerStruct.typeName = splitURL[ splitURL.length() - 1]; + } + layerStruct.uuid = layerMap["uuid"].toString(); + layerStruct.name = layerMap["name"].toString(); + layerStruct.typeName = layerMap["typename"].toString(); + layerStruct.title = layerMap["title"].toString(); + layers.append( layerStruct ); + } + } + return layers; +} + + +QStringList QgsGeoNodeRequest::serviceUrls( QString serviceType ) +{ + QStringList urls; + bool success = getLayers(); + + if ( !success ) + { + return urls; + } + + QList layers = parseLayers( this->response() ); + + Q_FOREACH ( QgsServiceLayerDetail layer, layers ) + { + QString url; + if ( serviceType.toLower() == "wms" ) + { + url = layer.wmsURL; + } + else if ( serviceType.toLower() == "wfs" ) + { + url = layer.wfsURL; + } + else if ( serviceType.toLower() == "xyz" ) + { + url = layer.xyzURL; + } + else + { + url = ""; + } + + if ( !url.contains( QLatin1String( "://" ) ) && url.length() > 0 ) + { + url.prepend( getProtocol() ); + } + if ( !urls.contains( url ) && url.length() > 0 ) + { + urls.append( url ); + } + } + + return urls; +} + + +QgsStringMap QgsGeoNodeRequest::serviceUrlData( QString serviceType ) +{ + QgsStringMap urls; + bool success = getLayers(); + + if ( !success ) + { + return urls; + } + QList layers = parseLayers( this->response() ); + + Q_FOREACH ( QgsServiceLayerDetail layer, layers ) + { + QString url; + + if ( serviceType.toLower() == "wms" ) + { + url = layer.wmsURL; + } + else if ( serviceType.toLower() == "wfs" ) + { + url = layer.wfsURL; + } + else if ( serviceType.toLower() == "xyz" ) + { + url = layer.xyzURL; + } + else + { + url = ""; + } + + QString layerName = layer.name; + if ( !url.contains( QLatin1String( "://" ) ) && url.length() > 0 ) + { + url.prepend( getProtocol() ); + } + if ( !urls.contains( url ) && url.length() > 0 ) + { + urls.insert( layerName, url ); + } + } + + return urls; +} + + +bool QgsGeoNodeRequest::request( QString endPoint ) +{ + abort(); + mIsAborted = false; + QgsMessageLog::logMessage( mBaseUrl, tr( "GeoNode" ) ); + QString url = mBaseUrl + endPoint; + QgsMessageLog::logMessage( url, tr( "GeoNode" ) ); + setProtocol( url.split( "://" )[0] ); + QUrl layerUrl( url ); + layerUrl.setScheme( getProtocol() ); + + mError.clear(); + + QNetworkRequest request( url ); + // Add authentication check here + + request.setAttribute( QNetworkRequest::CacheLoadControlAttribute, mForceRefresh ? QNetworkRequest::AlwaysNetwork : QNetworkRequest::PreferCache ); + request.setAttribute( QNetworkRequest::CacheSaveControlAttribute, true ); + + mGeoNodeReply = QgsNetworkAccessManager::instance()->get( request ); + + connect( mGeoNodeReply, &QNetworkReply::finished, this, &QgsGeoNodeRequest::replyFinished, Qt::DirectConnection ); + connect( mGeoNodeReply, &QNetworkReply::downloadProgress, this, &QgsGeoNodeRequest::replyProgress, Qt::DirectConnection ); + + QEventLoop loop; + connect( this, &QgsGeoNodeRequest::requestFinished, &loop, &QEventLoop::quit ); + + loop.exec( QEventLoop::ExcludeUserInputEvents ); + + return mError.isEmpty(); +} diff --git a/src/core/geonode/qgsgeonoderequest.h b/src/core/geonode/qgsgeonoderequest.h new file mode 100644 index 000000000000..aedbce7a9631 --- /dev/null +++ b/src/core/geonode/qgsgeonoderequest.h @@ -0,0 +1,112 @@ +/*************************************************************************** + qgsgeonoderequest.h + --------------------- + begin : Jul 2017 + copyright : (C) 2017 by Muhammad Yarjuna Rohmat, Ismail Sunni + email : rohmat at kartoza dot com, ismail at kartoza 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. * + * * + ***************************************************************************/ +#ifndef QGSGEONODEREQUEST_H +#define QGSGEONODEREQUEST_H + +#include "qgis.h" +#include "qgis_core.h" +#include + + +#include +#include + +struct CORE_EXPORT QgsServiceLayerDetail +{ +#ifdef SIP_RUN + % TypeHeaderCode +#include + % End +#endif + QUuid uuid; + QString name; + QString typeName; + QString title; + QString wmsURL; + QString wfsURL; + QString xyzURL; +}; + +class CORE_EXPORT QgsGeoNodeRequest : public QObject +{ + Q_OBJECT + public: + explicit QgsGeoNodeRequest( bool forceRefresh, QObject *parent = nullptr ); + QgsGeoNodeRequest( const QString &baseUrl, /*const QgsWmsAuthorization &auth,*/ bool forceRefresh, QObject *parent = nullptr ); + virtual ~QgsGeoNodeRequest(); + + bool request( QString endPoint ); + + bool getLayers(); + + QList parseLayers( QByteArray layerResponse ); + + // Obtain list of unique URL in the geonode + QStringList serviceUrls( QString serviceType ); + + // Obtain map of layer name and url for a service type + QgsStringMap serviceUrlData( QString serviceType ); + + QString lastError() const { return mError; } + + QByteArray response() const { return mHttpGeoNodeResponse; } + + QNetworkReply *reply() const { return mGeoNodeReply; } + + //! Abort network request immediately + void abort(); + + QString getProtocol() const; + void setProtocol( const QString &protocol ); + + signals: + //! \brief emit a signal to be caught by qgisapp and display a statusQString on status bar + void statusChanged( const QString &statusQString ); + + //! \brief emit a signal once the request is finished + void requestFinished(); + + protected slots: + void replyFinished(); + void replyProgress( qint64, qint64 ); + + protected: + + //! URL part of URI (httpuri) + QString mProtocol; + + //! URL part of URI (httpuri) + QString mBaseUrl; + +// QgsWmsAuthorization mAuth; + + //! The reply to the geonode request + QNetworkReply *mGeoNodeReply = nullptr; + + //! The error message associated with the last error. + QString mError; + + //! The mime type of the message + QString mErrorFormat; + + //! Response + QByteArray mHttpGeoNodeResponse; + + bool mIsAborted; + bool mForceRefresh; + +}; + +#endif // QGSGEONODEREQUEST_H diff --git a/src/core/qgsdataitemprovider.h b/src/core/qgsdataitemprovider.h index 9f2ddc1da24f..1cfe038115c1 100644 --- a/src/core/qgsdataitemprovider.h +++ b/src/core/qgsdataitemprovider.h @@ -18,6 +18,7 @@ #include "qgis_core.h" #include "qgis.h" +#include "qgsdataitem.h" class QgsDataItem; @@ -50,6 +51,9 @@ class CORE_EXPORT QgsDataItemProvider //! Caller takes responsibility of deleting created items. virtual QgsDataItem *createDataItem( const QString &path, QgsDataItem *parentItem ) = 0 SIP_FACTORY; + //! Create a vector of instances of QgsDataItem (or null) for given path and parent item. + //! Caller takes responsibility of deleting created items. + virtual QVector createDataItems( const QString &path, QgsDataItem *parentItem ) { Q_UNUSED( path ); Q_UNUSED( parentItem ); return QVector(); } }; #endif // QGSDATAITEMPROVIDER_H diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 8b98c620211d..50c510d63ef4 100755 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -150,6 +150,9 @@ SET(QGIS_GUI_SRCS editorwidgets/qgsvaluerelationsearchwidgetwrapper.cpp editorwidgets/qgsvaluerelationwidgetfactory.cpp + geonode/qgsgeonodenewconnection.cpp + geonode/qgsgeonodesourceselect.cpp + layertree/qgscustomlayerorderwidget.cpp layertree/qgslayertreeembeddedconfigwidget.cpp layertree/qgslayertreeembeddedwidgetregistry.cpp @@ -642,6 +645,9 @@ SET(QGIS_GUI_MOC_HDRS editorwidgets/qgsvaluerelationsearchwidgetwrapper.h editorwidgets/qgsvaluerelationwidgetwrapper.h + geonode/qgsgeonodenewconnection.h + geonode/qgsgeonodesourceselect.h + layertree/qgscustomlayerorderwidget.h layertree/qgslayertreeembeddedconfigwidget.h layertree/qgslayertreeembeddedwidgetsimpl.h @@ -759,6 +765,9 @@ SET(QGIS_GUI_HDRS editorwidgets/qgsvaluemapwidgetfactory.h editorwidgets/qgsvaluerelationwidgetfactory.h + geonode/qgsgeonodenewconnection.h + geonode/qgsgeonodesourceselect.h + layertree/qgslayertreeembeddedconfigwidget.h layertree/qgslayertreeembeddedwidgetregistry.h @@ -809,6 +818,7 @@ SET(QGIS_GUI_UI_HDRS ${CMAKE_CURRENT_BINARY_DIR}/../ui/ui_qgssqlcomposerdialogbase.h ${CMAKE_CURRENT_BINARY_DIR}/../ui/ui_qgssublayersdialogbase.h ${CMAKE_CURRENT_BINARY_DIR}/../ui/ui_qgstablewidgetuibase.h + ${CMAKE_CURRENT_BINARY_DIR}/../ui/ui_qgsgeonodesourceselectbase.h ) IF(ENABLE_MODELTEST) @@ -828,12 +838,14 @@ INCLUDE_DIRECTORIES( ${CMAKE_SOURCE_DIR}/src/gui/layertree ${CMAKE_SOURCE_DIR}/src/gui/layout ${CMAKE_SOURCE_DIR}/src/gui/effects + ${CMAKE_SOURCE_DIR}/src/gui/geonode ${CMAKE_SOURCE_DIR}/src/gui/ogr ${CMAKE_SOURCE_DIR}/src/core ${CMAKE_SOURCE_DIR}/src/core/annotations ${CMAKE_SOURCE_DIR}/src/core/auth ${CMAKE_SOURCE_DIR}/src/core/composer ${CMAKE_SOURCE_DIR}/src/core/fieldformatter + ${CMAKE_SOURCE_DIR}/src/core/geonode ${CMAKE_SOURCE_DIR}/src/core/geometry ${CMAKE_SOURCE_DIR}/src/core/layertree ${CMAKE_SOURCE_DIR}/src/core/layout diff --git a/src/gui/geonode/qgsgeonodenewconnection.cpp b/src/gui/geonode/qgsgeonodenewconnection.cpp new file mode 100644 index 000000000000..c5509c6c0566 --- /dev/null +++ b/src/gui/geonode/qgsgeonodenewconnection.cpp @@ -0,0 +1,266 @@ +/*************************************************************************** + qgsgeonodenewconnection.cpp + ------------------- + begin : Feb 2017 + copyright : (C) 2017 by Muhammad Yarjuna Rohmat, Ismail Sunni + email : rohmat at kartoza dot com, ismail at kartoza 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 +#include +#include "qgslogger.h" + +#include "qgsgeonodenewconnection.h" +#include "qgsauthmanager.h" +#include "qgsdatasourceuri.h" +#include "qgsgeonodeconnection.h" +#include "qgssettings.h" +#include "qgsgeonoderequest.h" + +QgsGeoNodeNewConnection::QgsGeoNodeNewConnection( QWidget *parent, const QString &connName, Qt::WindowFlags fl ) + : QDialog( parent, fl ) + , mOriginalConnName( connName ) + , mAuthConfigSelect( nullptr ) +{ + setupUi( this ); + + mBaseKey = QgsGeoNodeConnection::pathGeoNodeConnection(); + mCredentialsBaseKey = QgsGeoNodeConnection::pathGeoNodeConnection(); + + mAuthConfigSelect = new QgsAuthConfigSelect( this ); + tabAuth->insertTab( 1, mAuthConfigSelect, tr( "Configurations" ) ); + + cmbDpiMode->clear(); + cmbDpiMode->addItem( tr( "all" ) ); + cmbDpiMode->addItem( tr( "off" ) ); + cmbDpiMode->addItem( tr( "QGIS" ) ); + cmbDpiMode->addItem( tr( "UMN" ) ); + cmbDpiMode->addItem( tr( "GeoServer" ) ); + + cmbVersion->clear(); + cmbVersion->addItem( tr( "Auto-detect" ) ); + cmbVersion->addItem( tr( "1.0" ) ); + cmbVersion->addItem( tr( "1.1" ) ); + cmbVersion->addItem( tr( "2.0" ) ); + + if ( !connName.isEmpty() ) + { + // populate the dialog with the information stored for the connection + // populate the fields with the stored setting parameters + QgsSettings settings; + + QString key = mBaseKey + '/' + connName; + QString credentialsKey = mCredentialsBaseKey + '/' + connName; + txtName->setText( connName ); + txtUrl->setText( settings.value( key + "/url", "", QgsSettings::Providers ).toString() ); + + cbxIgnoreGetMapURI->setChecked( settings.value( key + "/wms/ignoreGetMapURI", false, QgsSettings::Providers ).toBool() ); + cbxWfsIgnoreAxisOrientation->setChecked( settings.value( key + "/wfs/ignoreAxisOrientation", false, QgsSettings::Providers ).toBool() ); + cbxWmsIgnoreAxisOrientation->setChecked( settings.value( key + "/wms/ignoreAxisOrientation", false, QgsSettings::Providers ).toBool() ); + cbxWfsInvertAxisOrientation->setChecked( settings.value( key + "/wfs/invertAxisOrientation", false, QgsSettings::Providers ).toBool() ); + cbxWmsInvertAxisOrientation->setChecked( settings.value( key + "/wms/invertAxisOrientation", false, QgsSettings::Providers ).toBool() ); + cbxIgnoreGetFeatureInfoURI->setChecked( settings.value( key + "/wms/ignoreGetFeatureInfoURI", false, QgsSettings::Providers ).toBool() ); + cbxSmoothPixmapTransform->setChecked( settings.value( key + "/wms/smoothPixmapTransform", false, QgsSettings::Providers ).toBool() ); + + int dpiIdx; + switch ( settings.value( key + "/dpiMode", 7, QgsSettings::Providers ).toInt() ) + { + case 0: // off + dpiIdx = 1; + break; + case 1: // QGIS + dpiIdx = 2; + break; + case 2: // UMN + dpiIdx = 3; + break; + case 4: // GeoServer + dpiIdx = 4; + break; + default: // other => all + dpiIdx = 0; + break; + } + cmbDpiMode->setCurrentIndex( dpiIdx ); + + QString version = settings.value( key + "/version", QLatin1String( "1.0.0" ), QgsSettings::Providers ).toString(); + int versionIdx = 0; // AUTO + if ( version == QLatin1String( "1.0.0" ) ) + versionIdx = 1; + else if ( version == QLatin1String( "1.1.0" ) ) + versionIdx = 2; + else if ( version == QLatin1String( "2.0.0" ) ) + versionIdx = 3; + cmbVersion->setCurrentIndex( versionIdx ); + + txtReferer->setText( settings.value( key + "/referer", "", QgsSettings::Providers ).toString() ); + txtMaxNumFeatures->setText( settings.value( key + "/maxnumfeatures", QgsSettings::Providers ).toString() ); + + txtUserName->setText( settings.value( credentialsKey + "/username", "", QgsSettings::Providers ).toString() ); + txtPassword->setText( settings.value( credentialsKey + "/password", "", QgsSettings::Providers ).toString() ); + + QString authcfg = settings.value( credentialsKey + "/authcfg", "", QgsSettings::Providers ).toString(); + mAuthConfigSelect->setConfigId( authcfg ); + if ( !authcfg.isEmpty() ) + { + tabAuth->setCurrentIndex( tabAuth->indexOf( mAuthConfigSelect ) ); + } + } + + // Adjust height + int w = width(); + adjustSize(); + resize( w, height() ); + + buttonBox->button( QDialogButtonBox::Ok )->setDisabled( true ); + connect( txtName, &QLineEdit::textChanged, this, &QgsGeoNodeNewConnection::okButtonBehavior ); + connect( txtUrl, &QLineEdit::textChanged, this, &QgsGeoNodeNewConnection::okButtonBehavior ); + connect( btnConnect, &QPushButton::clicked, this, &QgsGeoNodeNewConnection::testConnection ); +} + +void QgsGeoNodeNewConnection::accept() +{ + QgsSettings settings; + QString key = mBaseKey + '/' + txtName->text(); + QString credentialsKey = mCredentialsBaseKey + '/' + txtName->text(); + + // warn if entry was renamed to an existing connection + if ( ( mOriginalConnName.isNull() || mOriginalConnName.compare( txtName->text(), Qt::CaseInsensitive ) != 0 ) && + settings.contains( key + "/url", QgsSettings::Providers ) && + QMessageBox::question( this, + tr( "Save connection" ), + tr( "Should the existing connection %1 be overwritten?" ).arg( txtName->text() ), + QMessageBox::Ok | QMessageBox::Cancel ) == QMessageBox::Cancel ) + { + return; + } + + if ( !txtPassword->text().isEmpty() && + QMessageBox::question( this, + tr( "Saving passwords" ), + trUtf8( "WARNING: You have entered a password. It will be stored in unsecured plain text in your project files and your home directory (Unix-like OS) or user profile (Windows). If you want to avoid this, press Cancel and either:\n\na) Don't provide a password in the connection settings — it will be requested interactively when needed;\nb) Use the Configuration tab to add your credentials in an HTTP Basic Authentication method and store them in an encrypted database." ), + QMessageBox::Ok | QMessageBox::Cancel ) == QMessageBox::Cancel ) + { + return; + } + + // on rename delete original entry first + if ( !mOriginalConnName.isNull() && mOriginalConnName != key ) + { + // Manually add Section here + settings.remove( "providers/" + mBaseKey + '/' + mOriginalConnName ); + settings.remove( "providers/qgis//" + mCredentialsBaseKey + '/' + mOriginalConnName ); + settings.sync(); + } + + if ( !txtUrl->text().contains( "://" ) && + QMessageBox::information( + this, + tr( "Invalid URL" ), + tr( "Your URL doesn't contains protocol (e.g. http or https). Please add the protocol." ) ) == QMessageBox::Ok ) + { + return; + } + QUrl url( txtUrl->text() ); + + settings.setValue( key + "/url", url.toString(), QgsSettings::Providers ); + + settings.setValue( key + "/wfs/ignoreAxisOrientation", cbxWfsIgnoreAxisOrientation->isChecked(), QgsSettings::Providers ); + settings.setValue( key + "/wms/ignoreAxisOrientation", cbxWmsIgnoreAxisOrientation->isChecked(), QgsSettings::Providers ); + settings.setValue( key + "/wfs/invertAxisOrientation", cbxWfsInvertAxisOrientation->isChecked(), QgsSettings::Providers ); + settings.setValue( key + "/wms/invertAxisOrientation", cbxWmsInvertAxisOrientation->isChecked(), QgsSettings::Providers ); + + settings.setValue( key + "/wms/ignoreGetMapURI", cbxIgnoreGetMapURI->isChecked(), QgsSettings::Providers ); + settings.setValue( key + "/wms/smoothPixmapTransform", cbxSmoothPixmapTransform->isChecked(), QgsSettings::Providers ); + settings.setValue( key + "/wms/ignoreGetFeatureInfoURI", cbxIgnoreGetFeatureInfoURI->isChecked(), QgsSettings::Providers ); + + int dpiMode = 0; + switch ( cmbDpiMode->currentIndex() ) + { + case 0: // all => QGIS|UMN|GeoServer + dpiMode = 7; + break; + case 1: // off + dpiMode = 0; + break; + case 2: // QGIS + dpiMode = 1; + break; + case 3: // UMN + dpiMode = 2; + break; + case 4: // GeoServer + dpiMode = 4; + break; + } + + settings.setValue( key + "/wms/dpiMode", dpiMode, QgsSettings::Providers ); + settings.setValue( key + "/wms/referer", txtReferer->text(), QgsSettings::Providers ); + + QString version = QStringLiteral( "auto" ); + switch ( cmbVersion->currentIndex() ) + { + case 0: + version = QStringLiteral( "auto" ); + break; + case 1: + version = QStringLiteral( "1.0.0" ); + break; + case 2: + version = QStringLiteral( "1.1.0" ); + break; + case 3: + version = QStringLiteral( "2.0.0" ); + break; + } + + settings.setValue( key + "/wfs/version", version, QgsSettings::Providers ); + settings.setValue( key + "/wfs/maxnumfeatures", txtMaxNumFeatures->text(), QgsSettings::Providers ); + + settings.setValue( credentialsKey + "/username", txtUserName->text(), QgsSettings::Providers ); + settings.setValue( credentialsKey + "/password", txtPassword->text(), QgsSettings::Providers ); + + settings.setValue( credentialsKey + "/authcfg", mAuthConfigSelect->configId(), QgsSettings::Providers ); + + settings.setValue( mBaseKey + "/selected", txtName->text(), QgsSettings::Providers ); + + QDialog::accept(); +} + +void QgsGeoNodeNewConnection::okButtonBehavior( const QString &text ) +{ + Q_UNUSED( text ); + buttonBox->button( QDialogButtonBox::Ok )->setDisabled( txtName->text().isEmpty() || txtUrl->text().isEmpty() ); + buttonBox->button( QDialogButtonBox::Ok )->setEnabled( !txtName->text().isEmpty() && !txtUrl->text().isEmpty() ); +} + +void QgsGeoNodeNewConnection::testConnection() +{ + QApplication::setOverrideCursor( Qt::BusyCursor ); + QString url = txtUrl->text(); + QgsGeoNodeRequest geonodeRequest( url, true ); + bool success = geonodeRequest.getLayers(); + QApplication::restoreOverrideCursor(); + + if ( success ) + { + QMessageBox::information( this, + tr( "Test connection" ), + tr( "\nConnection to %1 was successful, \n\n%1 is a valid geonode instance.\n\n" ).arg( txtUrl->text() ) ); + } + else + { + QMessageBox::information( this, + tr( "Test connection" ), + tr( "\nConnection failed, \n\nplease check whether %1 is a valid geonode instance.\n\n" ).arg( txtUrl->text() ) ); + } +} diff --git a/src/gui/geonode/qgsgeonodenewconnection.h b/src/gui/geonode/qgsgeonodenewconnection.h new file mode 100644 index 000000000000..3af65c842cbe --- /dev/null +++ b/src/gui/geonode/qgsgeonodenewconnection.h @@ -0,0 +1,47 @@ +/*************************************************************************** + qgsgeonodenewconnection.h + ------------------- + begin : Feb 2017 + copyright : (C) 2017 by Muhammad Yarjuna Rohmat, Ismail Sunni + email : rohmat at kartoza dot com, ismail at kartoza 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. * + * * + ***************************************************************************/ + +#ifndef QGSGEONODENEWCONNECTION_H +#define QGSGEONODENEWCONNECTION_H + +#include "ui_qgsnewgeonodeconnectionbase.h" +#include "qgis_gui.h" +#include "qgsguiutils.h" +#include "qgsauthconfigselect.h" + +class GUI_EXPORT QgsGeoNodeNewConnection : public QDialog, private Ui::QgsNewGeoNodeConnectionBase +{ + Q_OBJECT + + public: + //! Constructor + QgsGeoNodeNewConnection( QWidget *parent = nullptr, const QString &connName = QString::null, Qt::WindowFlags fl = QgsGuiUtils::ModalDialogFlags ); + + public slots: + void accept() override; + void okButtonBehavior( const QString & ); + //! Test the connection using the parameters supplied + void testConnection(); + + private: + QString mBaseKey; + QString mCredentialsBaseKey; + QString mOriginalConnName; //store initial name to delete entry in case of rename + QgsAuthConfigSelect *mAuthConfigSelect = nullptr; +}; + +#endif //QGSGEONODENEWCONNECTION_H diff --git a/src/gui/geonode/qgsgeonodesourceselect.cpp b/src/gui/geonode/qgsgeonodesourceselect.cpp new file mode 100644 index 000000000000..800483b769fb --- /dev/null +++ b/src/gui/geonode/qgsgeonodesourceselect.cpp @@ -0,0 +1,505 @@ +/*************************************************************************** + qgsgeonodesourceselect.cpp + ------------------- + begin : Feb 2017 + copyright : (C) 2017 by Muhammad Yarjuna Rohmat, Ismail Sunni + email : rohmat at kartoza dot com, ismail at kartoza 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 "qgslogger.h" +#include "qgsmessagelog.h" +#include "qgsproviderregistry.h" + +#include "qgsgeonodesourceselect.h" +#include "qgsgeonodeconnection.h" +#include "qgsgeonoderequest.h" + +#include "qgsgeonodenewconnection.h" +#include "qgsmanageconnectionsdialog.h" + +#include +#include +#include +#include + +enum +{ + MODEL_IDX_TITLE, + MODEL_IDX_NAME, + MODEL_IDX_TYPE, + MODEL_IDX_WEB_SERVICE +}; + +QgsGeoNodeSourceSelect::QgsGeoNodeSourceSelect( QWidget *parent, Qt::WindowFlags fl, bool embeddedMode ) + : QDialog( parent, fl ) +{ + setupUi( this ); + + if ( embeddedMode != QgsProviderRegistry::WidgetMode::None ) + { + // For some obscure reasons hiding does not work! + // buttonBox->button( QDialogButtonBox::Close )->hide(); + buttonBox->removeButton( buttonBox->button( QDialogButtonBox::Close ) ); + } + + mAddButton = new QPushButton( tr( "&Add" ) ); + mAddButton->setEnabled( false ); + + buttonBox->addButton( mAddButton, QDialogButtonBox::ActionRole ); + + populateConnectionList(); + + connect( buttonBox, &QDialogButtonBox::rejected, this, &QgsGeoNodeSourceSelect::reject ); + connect( btnNew, &QPushButton::clicked, this, &QgsGeoNodeSourceSelect::addConnectionsEntryList ); + connect( btnEdit, &QPushButton::clicked, this, &QgsGeoNodeSourceSelect::modifyConnectionsEntryList ); + connect( btnDelete, &QPushButton::clicked, this, &QgsGeoNodeSourceSelect::deleteConnectionsEntryList ); + connect( btnConnect, &QPushButton::clicked, this, &QgsGeoNodeSourceSelect::connectToGeonodeConnection ); + connect( btnSave, &QPushButton::clicked, this, &QgsGeoNodeSourceSelect::saveGeonodeConnection ); + connect( btnLoad, &QPushButton::clicked, this, &QgsGeoNodeSourceSelect::loadGeonodeConnection ); + connect( lineFilter, &QLineEdit::textChanged, this, &QgsGeoNodeSourceSelect::filterChanged ); + connect( treeView, &QTreeView::clicked, this, &QgsGeoNodeSourceSelect::treeViewSelectionChanged ); + connect( mAddButton, &QPushButton::clicked, this, &QgsGeoNodeSourceSelect::addButtonClicked ); + + mItemDelegate = new QgsGeonodeItemDelegate( treeView ); + treeView->setItemDelegate( mItemDelegate ); + + mModel = new QStandardItemModel(); + mModel->setHorizontalHeaderItem( MODEL_IDX_TITLE, new QStandardItem( tr( "Title" ) ) ); + mModel->setHorizontalHeaderItem( MODEL_IDX_NAME, new QStandardItem( tr( "Name" ) ) ); + mModel->setHorizontalHeaderItem( MODEL_IDX_TYPE, new QStandardItem( tr( "Type" ) ) ); + mModel->setHorizontalHeaderItem( MODEL_IDX_WEB_SERVICE, new QStandardItem( tr( "Web Service" ) ) ); + + mModelProxy = new QSortFilterProxyModel( this ); + mModelProxy->setSourceModel( mModel ); + mModelProxy->setSortCaseSensitivity( Qt::CaseInsensitive ); + treeView->setModel( mModelProxy ); +} + +QgsGeoNodeSourceSelect::~QgsGeoNodeSourceSelect() {} + +void QgsGeoNodeSourceSelect::addConnectionsEntryList() +{ + QgsGeoNodeNewConnection *nc = new QgsGeoNodeNewConnection( this ); + + if ( nc->exec() ) + { + populateConnectionList(); + emit connectionsChanged(); + } + + delete nc; +} + +void QgsGeoNodeSourceSelect::modifyConnectionsEntryList() +{ + QgsGeoNodeNewConnection *nc = new QgsGeoNodeNewConnection( this, cmbConnections->currentText() ); + nc->setWindowTitle( tr( "Modify GeoNode connection" ) ); + + if ( nc->exec() ) + { + populateConnectionList(); + emit connectionsChanged(); + } +} + +void QgsGeoNodeSourceSelect::deleteConnectionsEntryList() +{ + QString msg = tr( "Are you sure you want to remove the %1 connection and all associated settings?" ) + .arg( cmbConnections->currentText() ); + QMessageBox::StandardButton result = QMessageBox::information( this, tr( "Confirm Delete" ), msg, QMessageBox::Ok | QMessageBox::Cancel ); + if ( result == QMessageBox::Ok ) + { + QgsGeoNodeConnection::deleteConnection( cmbConnections->currentText() ); + cmbConnections->removeItem( cmbConnections->currentIndex() ); + if ( mModel ) + { + mModel->removeRows( 0, mModel->rowCount() ); + } + emit connectionsChanged(); + + if ( cmbConnections->count() > 0 ) + { + // Connections available - enable various buttons + btnConnect->setEnabled( true ); + btnEdit->setEnabled( true ); + btnDelete->setEnabled( true ); + btnSave->setEnabled( true ); + } + else + { + // No connections available - disable various buttons + btnConnect->setEnabled( false ); + btnEdit->setEnabled( false ); + btnDelete->setEnabled( false ); + btnSave->setEnabled( false ); + } + } +} + +void QgsGeoNodeSourceSelect::populateConnectionList() +{ + cmbConnections->clear(); + cmbConnections->addItems( QgsGeoNodeConnection::connectionList() ); + + setConnectionListPosition(); +} + +void QgsGeoNodeSourceSelect::setConnectionListPosition() +{ + QString toSelect = QgsGeoNodeConnection::selectedConnection(); + + cmbConnections->setCurrentIndex( cmbConnections->findText( toSelect ) ); + + if ( cmbConnections->currentIndex() < 0 ) + { + if ( toSelect.isNull() ) + cmbConnections->setCurrentIndex( 0 ); + else + cmbConnections->setCurrentIndex( cmbConnections->count() - 1 ); + } + + if ( cmbConnections->count() == 0 ) + { + // No connections - disable various buttons + btnConnect->setEnabled( false ); + btnEdit->setEnabled( false ); + btnDelete->setEnabled( false ); + btnSave->setEnabled( false ); + } + else + { + // Connections - enable various buttons + btnConnect->setEnabled( true ); + btnEdit->setEnabled( true ); + btnDelete->setEnabled( true ); + btnSave->setEnabled( true ); + } +} + +void QgsGeoNodeSourceSelect::connectToGeonodeConnection() +{ + QApplication::setOverrideCursor( Qt::BusyCursor ); + QgsGeoNodeConnection connection( cmbConnections->currentText() ); + + QString url = connection.uri().param( "url" ); + QgsGeoNodeRequest geonodeRequest( url, true ); + + QApplication::setOverrideCursor( Qt::WaitCursor ); + bool success = geonodeRequest.getLayers(); + QApplication::restoreOverrideCursor(); + + if ( success ) + { + QgsMessageLog::logMessage( QStringLiteral( "Success" ), tr( "GeoNode" ) ); + } + else + { + QgsMessageLog::logMessage( QStringLiteral( "Failed" ), tr( "GeoNode" ) ); + } + + QByteArray ba = geonodeRequest.response(); + + QList layers = geonodeRequest.parseLayers( ba ); + + if ( mModel ) + { + mModel->removeRows( 0, mModel->rowCount() ); + } + + if ( !layers.isEmpty() ) + { + Q_FOREACH ( const QgsServiceLayerDetail &layer, layers ) + { + QUuid uuid = layer.uuid; + + QString layerName = layer.name; + + QString wmsURL = layer.wmsURL; + QString wfsURL = layer.wfsURL; + QString xyzURL = layer.xyzURL; + + if ( wmsURL.length() > 0 ) + { + QStandardItem *titleItem = new QStandardItem( layer.title ); + QStandardItem *nameItem; + if ( layer.name > 0 ) + { + nameItem = new QStandardItem( layer.name ); + } + else + { + nameItem = new QStandardItem( layer.title ); + } + QStandardItem *serviceTypeItem = new QStandardItem( tr( "Layer" ) ); + QStandardItem *webServiceTypeItem = new QStandardItem( tr( "WMS" ) ); + + QString typeName = layer.typeName; + + titleItem->setData( uuid, Qt::UserRole + 1 ); + titleItem->setData( wmsURL, Qt::UserRole + 2 ); + titleItem->setData( typeName, Qt::UserRole + 3 ); + typedef QList< QStandardItem * > StandardItemList; + mModel->appendRow( StandardItemList() << titleItem << nameItem << serviceTypeItem << webServiceTypeItem ); + } + else + { + qDebug() << "Layer " << layer.title << " does not have WMS url."; + } + if ( wfsURL.length() > 0 ) + { + QStandardItem *titleItem = new QStandardItem( layer.title ); + QStandardItem *nameItem; + if ( layer.name.length() > 0 ) + { + nameItem = new QStandardItem( layer.name ); + } + else + { + nameItem = new QStandardItem( layer.title ); + } + QStandardItem *serviceTypeItem = new QStandardItem( tr( "Layer" ) ); + QStandardItem *webServiceTypeItem = new QStandardItem( tr( "WFS" ) ); + + QString typeName = layer.typeName; + + titleItem->setData( uuid, Qt::UserRole + 1 ); + titleItem->setData( wfsURL, Qt::UserRole + 2 ); + titleItem->setData( typeName, Qt::UserRole + 3 ); + typedef QList< QStandardItem * > StandardItemList; + mModel->appendRow( StandardItemList() << titleItem << nameItem << serviceTypeItem << webServiceTypeItem ); + } + else + { + qDebug() << "Layer " << layer.title << " does not have WFS url."; + } + if ( xyzURL.length() > 0 ) + { + QStandardItem *titleItem = new QStandardItem( layer.title ); + QStandardItem *nameItem; + if ( layer.name.length() > 0 ) + { + nameItem = new QStandardItem( layer.name ); + } + else + { + nameItem = new QStandardItem( layer.title ); + } + QStandardItem *serviceTypeItem = new QStandardItem( tr( "Layer" ) ); + QStandardItem *webServiceTypeItem = new QStandardItem( tr( "XYZ" ) ); + + QString typeName = layer.typeName; + + titleItem->setData( uuid, Qt::UserRole + 1 ); + titleItem->setData( xyzURL, Qt::UserRole + 2 ); + titleItem->setData( typeName, Qt::UserRole + 3 ); + typedef QList< QStandardItem * > StandardItemList; + mModel->appendRow( StandardItemList() << titleItem << nameItem << serviceTypeItem << webServiceTypeItem ); + } + else + { + qDebug() << "Layer " << layer.title << " does not have XYZ url."; + } + } + } + + else + { + QMessageBox *box = new QMessageBox( QMessageBox::Critical, tr( "Error" ), tr( "Cannot get any feature services" ), QMessageBox::Ok, this ); + box->setAttribute( Qt::WA_DeleteOnClose ); + box->setModal( true ); + box->setObjectName( QStringLiteral( "GeonodeCapabilitiesErrorBox" ) ); + box->open(); + } + + treeView->resizeColumnToContents( MODEL_IDX_TITLE ); + treeView->resizeColumnToContents( MODEL_IDX_NAME ); + treeView->resizeColumnToContents( MODEL_IDX_TYPE ); + treeView->resizeColumnToContents( MODEL_IDX_WEB_SERVICE ); + for ( int i = MODEL_IDX_TITLE; i < MODEL_IDX_WEB_SERVICE; i++ ) + { + if ( treeView->columnWidth( i ) > 210 ) + { + treeView->setColumnWidth( i, 210 ); + } + } + QApplication::restoreOverrideCursor(); +} + +void QgsGeoNodeSourceSelect::saveGeonodeConnection() +{ + QgsManageConnectionsDialog dlg( this, QgsManageConnectionsDialog::Export, QgsManageConnectionsDialog::GeoNode ); + dlg.exec(); +} + +void QgsGeoNodeSourceSelect::loadGeonodeConnection() +{ + QString fileName = QFileDialog::getOpenFileName( this, tr( "Load connections" ), QDir::homePath(), + tr( "XML files (*.xml *XML)" ) ); + if ( fileName.isEmpty() ) + { + return; + } + + QgsManageConnectionsDialog dlg( this, QgsManageConnectionsDialog::Import, QgsManageConnectionsDialog::GeoNode, fileName ); + dlg.exec(); + populateConnectionList(); + emit connectionsChanged(); +} + +void QgsGeoNodeSourceSelect::filterChanged( const QString &text ) +{ + QRegExp::PatternSyntax mySyntax = QRegExp::PatternSyntax( QRegExp::RegExp ); + Qt::CaseSensitivity myCaseSensitivity = Qt::CaseInsensitive; + QRegExp myRegExp( text, myCaseSensitivity, mySyntax ); + mModelProxy->setFilterRegExp( myRegExp ); + mModelProxy->sort( mModelProxy->sortColumn(), mModelProxy->sortOrder() ); +} + +void QgsGeoNodeSourceSelect::treeViewSelectionChanged() +{ + QModelIndex currentIndex = treeView->selectionModel()->currentIndex(); + if ( !currentIndex.isValid() ) + { + qDebug() << "Current index is invalid"; + return; + } + mAddButton->setEnabled( false ); + QModelIndexList modelIndexList = treeView->selectionModel()->selectedRows(); + for ( int i = 0; i < modelIndexList.size(); i++ ) + { + QModelIndex idx = mModelProxy->mapToSource( modelIndexList[i] ); + if ( !idx.isValid() ) + { + continue; + } + int row = idx.row(); + QString typeItem = mModel->item( row, MODEL_IDX_TYPE )->text(); + if ( typeItem == tr( "Layer" ) ) + { + // Enable if there is a layer selected + mAddButton->setEnabled( true ); + return; + } + } + +} + +void QgsGeoNodeSourceSelect::addButtonClicked() +{ + qDebug() << "Add button clicked"; + QApplication::setOverrideCursor( Qt::BusyCursor ); + // Get selected entry in treeview + QModelIndex currentIndex = treeView->selectionModel()->currentIndex(); + if ( !currentIndex.isValid() ) + { + qDebug() << "Current index is invalid"; + return; + } + + QgsGeoNodeConnection connection( cmbConnections->currentText() ); + QModelIndexList modelIndexList = treeView->selectionModel()->selectedRows(); + for ( int i = 0; i < modelIndexList.size(); i++ ) + { + QModelIndex idx = mModelProxy->mapToSource( modelIndexList[i] ); + if ( !idx.isValid() ) + { + continue; + } + int row = idx.row(); + + qDebug() << "Model index row " << row; + + QString typeItem = mModel->item( row, MODEL_IDX_TYPE )->text(); + if ( typeItem == tr( "Map" ) ) + { + qDebug() << "Skip adding map."; + continue; + } + QString serviceURL = mModel->item( row, MODEL_IDX_TITLE )->data( Qt::UserRole + 2 ).toString(); + QString titleName = mModel->item( row, MODEL_IDX_TITLE )->text(); + QString layerName = mModel->item( row, MODEL_IDX_NAME )->text(); + QString webServiceType = mModel->item( row, MODEL_IDX_WEB_SERVICE )->text(); + + if ( cbxUseTitleLayerName->isChecked() && !titleName.isEmpty() ) + { + QString layerName = titleName; + } + + qDebug() << "Layer name: " << layerName << " Type: " << webServiceType; + + if ( webServiceType == "WMS" ) + { + qDebug() << "Adding WMS layer of " << layerName; + QgsDataSourceUri uri; + uri.setParam( QStringLiteral( "url" ), serviceURL ); + + // Set static first, to see that it works. Need to think about the UI also. + QString format( "image/png" ); + QString crs( "EPSG:4326" ); + QString styles( "" ); + QString contextualWMSLegend( "0" ); + + uri.setParam( QStringLiteral( "contextualWMSLegend" ), contextualWMSLegend ); + uri.setParam( QStringLiteral( "layers" ), layerName ); + uri.setParam( QStringLiteral( "styles" ), styles ); + uri.setParam( QStringLiteral( "format" ), format ); + uri.setParam( QStringLiteral( "crs" ), crs ); + + QgsDebugMsg( "Add WMS from GeoNode : " + uri.encodedUri() ); + emit addRasterLayer( uri.encodedUri(), layerName, QStringLiteral( "wms" ) ); + } + else if ( webServiceType == "WFS" ) + { + qDebug() << "Adding WFS layer of " << layerName; + + // Set static first, to see that it works. Need to think about the UI also. + QString typeName = mModel->item( row, 0 )->data( Qt::UserRole + 3 ).toString(); + QString crs( "EPSG:4326" ); + + // typeName, titleName, sql, + // Build url for WFS + // restrictToRequestBBOX='1' srsname='EPSG:26719' typename='geonode:cab_mun' url='http://demo.geonode.org/geoserver/geonode/wms' table=\"\" sql=" + QString uri; + uri += QStringLiteral( " restrictToRequestBBOX='1'" ); + uri += QStringLiteral( " srsname='%1'" ).arg( crs ); + if ( serviceURL.contains( "qgis-server" ) ) + { + // I need to do this since the typename used in qgis-server is without the workspace. + QString qgisServerTypeName = QString( typeName ).split( ":" ).last(); + uri += QStringLiteral( " typename='%1'" ).arg( qgisServerTypeName ); + } + else + { + uri += QStringLiteral( " typename='%1'" ).arg( typeName ); + } + uri += QStringLiteral( " url='%1'" ).arg( serviceURL ); + uri += QStringLiteral( " table=\"\"" ); + uri += QStringLiteral( " sql=" ); + + QgsMessageLog::logMessage( "Add WFS from GeoNode : " + uri + " and typename: " + typeName, tr( "GeoNode" ) ); + emit addWfsLayer( uri, typeName, "WFS" ); + } + else if ( webServiceType == "XYZ" ) + { + QgsDebugMsg( "XYZ Url: " + serviceURL ); + QgsDebugMsg( "Add XYZ from GeoNode : " + serviceURL ); + QgsDataSourceUri uri; + uri.setParam( QStringLiteral( "url" ), serviceURL ); + uri.setParam( QStringLiteral( "type" ), QStringLiteral( "xyz" ) ); + uri.setParam( QStringLiteral( "zmin" ), "0" ); + uri.setParam( QStringLiteral( "zmax" ), "18" ); + emit addRasterLayer( uri.encodedUri(), layerName, QStringLiteral( "wms" ) ); + } + } + + QApplication::restoreOverrideCursor(); +} diff --git a/src/gui/geonode/qgsgeonodesourceselect.h b/src/gui/geonode/qgsgeonodesourceselect.h new file mode 100644 index 000000000000..005b65e220f9 --- /dev/null +++ b/src/gui/geonode/qgsgeonodesourceselect.h @@ -0,0 +1,88 @@ +/*************************************************************************** + qgsgeonodesourceselect.h + ------------------- + begin : Feb 2017 + copyright : (C) 2017 by Muhammad Yarjuna Rohmat, Ismail Sunni + email : rohmat at kartoza dot com, ismail at kartoza 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. * + * * + ***************************************************************************/ + +#ifndef QGSGEONODESOURCESELECT_H +#define QGSGEONODESOURCESELECT_H + +#include +#include +#include +#include "ui_qgsgeonodesourceselectbase.h" +#include "qgis_gui.h" + +class GUI_EXPORT QgsGeonodeItemDelegate : public QItemDelegate +{ + Q_OBJECT + + public: + explicit QgsGeonodeItemDelegate( QObject *parent = nullptr ) : QItemDelegate( parent ) { } +}; + +class GUI_EXPORT QgsGeoNodeSourceSelect: public QDialog, private Ui::QgsGeonodeSourceSelectBase +{ + Q_OBJECT + + public: + + QgsGeoNodeSourceSelect( QWidget *parent, Qt::WindowFlags fl, bool embeddedMode = false ); + ~QgsGeoNodeSourceSelect(); + + signals: + void connectionsChanged(); + void addRasterLayer( const QString &rasterLayerPath, + const QString &baseName, + const QString &providerKey ); + void addRasterLayer(); + + void addWfsLayer( + const QString &uri, + const QString &layerName, + const QString &providerKey ); + + private: + QgsGeoNodeSourceSelect(); //default constructor is forbidden + + /** Stores the available CRS for a server connections. + The first string is the typename, the corresponding list + stores the CRS for the typename in the form 'EPSG:XXXX'*/ + QMap mAvailableCRS; + QString mUri; // data source URI + QgsGeonodeItemDelegate *mItemDelegate = nullptr; + QStandardItemModel *mModel = nullptr; + QSortFilterProxyModel *mModelProxy = nullptr; + QPushButton *mBuildQueryButton = nullptr; + QPushButton *mAddButton = nullptr; + QModelIndex mSQLIndex; + + private slots: + void addConnectionsEntryList(); + void modifyConnectionsEntryList(); + void deleteConnectionsEntryList(); + void connectToGeonodeConnection(); + void saveGeonodeConnection(); + void loadGeonodeConnection(); + void filterChanged( const QString &text ); + void treeViewSelectionChanged(); + void addButtonClicked(); + + void populateConnectionList(); + void setConnectionListPosition(); + +}; + + +#endif diff --git a/src/gui/qgsdatasourcemanagerdialog.cpp b/src/gui/qgsdatasourcemanagerdialog.cpp index 197bd72e7d0a..7deb320924cf 100644 --- a/src/gui/qgsdatasourcemanagerdialog.cpp +++ b/src/gui/qgsdatasourcemanagerdialog.cpp @@ -24,6 +24,7 @@ #include "qgsproviderregistry.h" #include "qgsabstractdatasourcewidget.h" #include "qgsmapcanvas.h" +#include "qgsgeonodesourceselect.h" QgsDataSourceManagerDialog::QgsDataSourceManagerDialog( QWidget *parent, QgsMapCanvas *canvas, Qt::WindowFlags fl ) : QgsOptionsDialogBase( QStringLiteral( "Data Source Manager" ), parent, fl ), @@ -98,6 +99,14 @@ QgsDataSourceManagerDialog::QgsDataSourceManagerDialog( QWidget *parent, QgsMapC addVectorProviderDialog( QStringLiteral( "arcgisfeatureserver" ), tr( "ArcGIS Feature Server" ), QStringLiteral( "/mActionAddAfsLayer.svg" ) ); + QDialog *geonodeDialog = new QgsGeoNodeSourceSelect( this, Qt::Widget, QgsProviderRegistry::WidgetMode::Embedded ); + dlg = addDialog( geonodeDialog, QStringLiteral( "geonode" ), tr( "GeoNode" ), QStringLiteral( "/mActionAddGeonodeLayer.svg" ) ); + + if ( dlg ) + { + connect( dlg, SIGNAL( addRasterLayer( QString, QString, QString ) ), this, SLOT( rasterLayerAdded( QString, QString, QString ) ) ); + connect( dlg, SIGNAL( addWfsLayer( QString, QString, QString ) ), this, SLOT( vectorLayerAdded( QString, QString, QString ) ) ); + } } QgsDataSourceManagerDialog::~QgsDataSourceManagerDialog() @@ -148,6 +157,15 @@ void QgsDataSourceManagerDialog::vectorLayersAdded( const QStringList &layerQStr emit addVectorLayers( layerQStringList, enc, dataSourceType ); } +QDialog *QgsDataSourceManagerDialog::addDialog( QDialog *dialog, QString const key, QString const name, QString const icon, QString title ) +{ + mPageNames.append( key ); + ui->mOptionsStackedWidget->addWidget( dialog ); + QListWidgetItem *layerItem = new QListWidgetItem( name, ui->mOptionsListWidget ); + layerItem->setToolTip( title.isEmpty() ? tr( "Add %1 layer" ).arg( name ) : title ); + layerItem->setIcon( QgsApplication::getThemeIcon( icon ) ); + return dialog; +} QgsAbstractDataSourceWidget *QgsDataSourceManagerDialog::providerDialog( const QString providerKey, const QString providerName, const QString icon, QString title ) { diff --git a/src/gui/qgsdatasourcemanagerdialog.h b/src/gui/qgsdatasourcemanagerdialog.h index 0deb9cf9e95e..0f1c12342bde 100644 --- a/src/gui/qgsdatasourcemanagerdialog.h +++ b/src/gui/qgsdatasourcemanagerdialog.h @@ -114,6 +114,8 @@ class GUI_EXPORT QgsDataSourceManagerDialog : public QgsOptionsDialogBase, priva void providerDialogsRefreshRequested(); private: + //! Add a provider dialog + QDialog *addDialog( QDialog *dialog, QString const key, QString const name, QString const icon, QString title = QString() ); // Return the dialog from the provider QgsAbstractDataSourceWidget *providerDialog( const QString providerKey, const QString providerName, const QString icon, QString title = QString() ); QgsAbstractDataSourceWidget *addDbProviderDialog( QString const providerKey, QString const providerName, QString const icon, QString title = QString() ); diff --git a/src/gui/qgsmanageconnectionsdialog.cpp b/src/gui/qgsmanageconnectionsdialog.cpp index f30c0df9fae8..b53f53c1ab6d 100644 --- a/src/gui/qgsmanageconnectionsdialog.cpp +++ b/src/gui/qgsmanageconnectionsdialog.cpp @@ -130,6 +130,9 @@ void QgsManageConnectionsDialog::doExportImport() case DB2: doc = saveDb2Connections( items ); break; + case GeoNode: + doc = saveGeonodeConnections( items ); + break; } QFile file( mFileName ); @@ -195,6 +198,9 @@ void QgsManageConnectionsDialog::doExportImport() case DB2: loadDb2Connections( doc, items ); break; + case GeoNode: + loadGeonodeConnections( doc, items ); + break; } // clear connections list and close window listConnections->clear(); @@ -233,6 +239,9 @@ bool QgsManageConnectionsDialog::populateConnections() case DB2: settings.beginGroup( QStringLiteral( "/DB2/connections" ) ); break; + case GeoNode: + settings.beginGroup( QStringLiteral( "/qgis/connections-geonode" ) ); + break; } QStringList keys = settings.childGroups(); QStringList::Iterator it = keys.begin(); @@ -336,6 +345,14 @@ bool QgsManageConnectionsDialog::populateConnections() return false; } break; + case GeoNode: + if ( root.tagName() != QLatin1String( "qgsGeoNodeConnections" ) ) + { + QMessageBox::information( this, tr( "Loading connections" ), + tr( "The file is not a GeoNode connections exchange file." ) ); + return false; + } + break; } QDomElement child = root.firstChildElement(); @@ -580,6 +597,31 @@ QDomDocument QgsManageConnectionsDialog::saveDb2Connections( const QStringList & return doc; } +QDomDocument QgsManageConnectionsDialog::saveGeonodeConnections( const QStringList &connections ) +{ + QDomDocument doc( QStringLiteral( "connections" ) ); + QDomElement root = doc.createElement( QStringLiteral( "qgsGeoNodeConnections" ) ); + root.setAttribute( QStringLiteral( "version" ), QStringLiteral( "1.0" ) ); + doc.appendChild( root ); + + QgsSettings settings; + QString path; + for ( int i = 0; i < connections.count(); ++i ) + { + path = QStringLiteral( "/qgis/connections-geonode/" ); + QDomElement el = doc.createElement( QStringLiteral( "geonode" ) ); + el.setAttribute( QStringLiteral( "name" ), connections[ i ] ); + el.setAttribute( QStringLiteral( "url" ), settings.value( path + connections[ i ] + "/url", "" ).toString() ); + + path = QStringLiteral( "/qgis/GeoNode/" ); + el.setAttribute( QStringLiteral( "username" ), settings.value( path + connections[ i ] + "/username", "" ).toString() ); + el.setAttribute( QStringLiteral( "password" ), settings.value( path + connections[ i ] + "/password", "" ).toString() ); + root.appendChild( el ); + } + + return doc; +} + void QgsManageConnectionsDialog::loadOWSConnections( const QDomDocument &doc, const QStringList &items, const QString &service ) { QDomElement root = doc.documentElement(); @@ -1103,6 +1145,87 @@ void QgsManageConnectionsDialog::loadDb2Connections( const QDomDocument &doc, co child = child.nextSiblingElement(); } } + +void QgsManageConnectionsDialog::loadGeonodeConnections( const QDomDocument &doc, const QStringList &items ) +{ + QDomElement root = doc.documentElement(); + if ( root.tagName() != QLatin1String( "qgsGeoNodeConnections" ) ) + { + QMessageBox::information( this, tr( "Loading connections" ), + tr( "The file is not a GeoNode connections exchange file." ) ); + return; + } + + QString connectionName; + QgsSettings settings; + settings.beginGroup( QStringLiteral( "/qgis/connections-geonode" ) ); + QStringList keys = settings.childGroups(); + settings.endGroup(); + QDomElement child = root.firstChildElement(); + bool prompt = true; + bool overwrite = true; + + while ( !child.isNull() ) + { + connectionName = child.attribute( QStringLiteral( "name" ) ); + if ( !items.contains( connectionName ) ) + { + child = child.nextSiblingElement(); + continue; + } + + // check for duplicates + if ( keys.contains( connectionName ) && prompt ) + { + int res = QMessageBox::warning( this, + tr( "Loading connections" ), + tr( "Connection with name '%1' already exists. Overwrite?" ) + .arg( connectionName ), + QMessageBox::Yes | QMessageBox::YesToAll | QMessageBox::No | QMessageBox::NoToAll | QMessageBox::Cancel ); + + switch ( res ) + { + case QMessageBox::Cancel: + return; + case QMessageBox::No: + child = child.nextSiblingElement(); + continue; + case QMessageBox::Yes: + overwrite = true; + break; + case QMessageBox::YesToAll: + prompt = false; + overwrite = true; + break; + case QMessageBox::NoToAll: + prompt = false; + overwrite = false; + break; + } + } + + if ( keys.contains( connectionName ) && !overwrite ) + { + child = child.nextSiblingElement(); + continue; + } + + // no dups detected or overwrite is allowed + settings.beginGroup( QStringLiteral( "/qgis/connections-geonode" ) ); + settings.setValue( QString( '/' + connectionName + "/url" ), child.attribute( QStringLiteral( "url" ) ) ); + settings.endGroup(); + + if ( !child.attribute( QStringLiteral( "username" ) ).isEmpty() ) + { + settings.beginGroup( "/qgis/GeoNode/" + connectionName ); + settings.setValue( QStringLiteral( "/username" ), child.attribute( QStringLiteral( "username" ) ) ); + settings.setValue( QStringLiteral( "/password" ), child.attribute( QStringLiteral( "password" ) ) ); + settings.endGroup(); + } + child = child.nextSiblingElement(); + } +} + void QgsManageConnectionsDialog::selectAll() { listConnections->selectAll(); diff --git a/src/gui/qgsmanageconnectionsdialog.h b/src/gui/qgsmanageconnectionsdialog.h index 835b0ccc4b06..36f907223d8c 100644 --- a/src/gui/qgsmanageconnectionsdialog.h +++ b/src/gui/qgsmanageconnectionsdialog.h @@ -47,6 +47,7 @@ class GUI_EXPORT QgsManageConnectionsDialog : public QDialog, private Ui::QgsMan DB2, WCS, Oracle, + GeoNode }; // constructor @@ -69,6 +70,7 @@ class GUI_EXPORT QgsManageConnectionsDialog : public QDialog, private Ui::QgsMan QDomDocument saveMssqlConnections( const QStringList &connections ); QDomDocument saveOracleConnections( const QStringList &connections ); QDomDocument saveDb2Connections( const QStringList &connections ); + QDomDocument saveGeonodeConnections( const QStringList &connections ); void loadOWSConnections( const QDomDocument &doc, const QStringList &items, const QString &service ); void loadWfsConnections( const QDomDocument &doc, const QStringList &items ); @@ -76,6 +78,7 @@ class GUI_EXPORT QgsManageConnectionsDialog : public QDialog, private Ui::QgsMan void loadMssqlConnections( const QDomDocument &doc, const QStringList &items ); void loadOracleConnections( const QDomDocument &doc, const QStringList &items ); void loadDb2Connections( const QDomDocument &doc, const QStringList &items ); + void loadGeonodeConnections( const QDomDocument &doc, const QStringList &items ); QString mFileName; Mode mDialogMode; diff --git a/src/gui/qgsnewhttpconnection.cpp b/src/gui/qgsnewhttpconnection.cpp index 3795080a0295..c9fc668aeefe 100644 --- a/src/gui/qgsnewhttpconnection.cpp +++ b/src/gui/qgsnewhttpconnection.cpp @@ -171,6 +171,7 @@ QgsNewHttpConnection::QgsNewHttpConnection( if ( mBaseKey != QLatin1String( "qgis/connections-wfs/" ) ) { + lblVersion->setVisible( false ); cmbVersion->setVisible( false ); mGroupBox->layout()->removeWidget( cmbVersion ); lblMaxNumFeatures->setVisible( false ); diff --git a/src/providers/ows/CMakeLists.txt b/src/providers/ows/CMakeLists.txt index da2deac23c23..e43c2cf3c480 100644 --- a/src/providers/ows/CMakeLists.txt +++ b/src/providers/ows/CMakeLists.txt @@ -1,21 +1,25 @@ SET(OWS_SRCS qgsowsprovider.cpp qgsowsdataitems.cpp -) + qgsgeonodedataitems.cpp) SET(OWS_MOC_HDRS qgsowsprovider.h qgsowsdataitems.h + qgsgeonodedataitems.h ) INCLUDE_DIRECTORIES ( + ${CMAKE_SOURCE_DIR}/src/gui/geonode ${CMAKE_SOURCE_DIR}/src/core ${CMAKE_SOURCE_DIR}/src/core/expression ${CMAKE_SOURCE_DIR}/src/core/geometry + ${CMAKE_SOURCE_DIR}/src/core/geonode ${CMAKE_SOURCE_DIR}/src/core/auth ${CMAKE_SOURCE_DIR}/src/core/metadata ${CMAKE_SOURCE_DIR}/src/gui ${CMAKE_SOURCE_DIR}/src/gui/auth + ${CMAKE_BINARY_DIR}/src/app ${CMAKE_BINARY_DIR}/src/core ${CMAKE_BINARY_DIR}/src/gui ${CMAKE_BINARY_DIR}/src/ui @@ -30,6 +34,8 @@ ADD_LIBRARY (owsprovider MODULE ${OWS_SRCS} ${OWS_MOC_SRCS}) TARGET_LINK_LIBRARIES (owsprovider qgis_core + qgis_gui + qgis_app ) IF (WITH_GUI) diff --git a/src/providers/ows/qgsgeonodedataitems.cpp b/src/providers/ows/qgsgeonodedataitems.cpp new file mode 100644 index 000000000000..ec9c565fbc18 --- /dev/null +++ b/src/providers/ows/qgsgeonodedataitems.cpp @@ -0,0 +1,273 @@ +/*************************************************************************** + qgsgeonodedataitems.cpp + --------------------- + begin : Feb 2017 + copyright : (C) 2017 by Muhammad Yarjuna Rohmat, Ismail Sunni + email : rohmat at kartoza dot com, ismail at kartoza 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 "qgsowsdataitems.h" +#include "qgslogger.h" +#include "qgsgeonodedataitems.h" +#include "qgsproviderregistry.h" +#include "qgsnewhttpconnection.h" +#include "qgsgeonodenewconnection.h" +#include "qgsgeonoderequest.h" + +typedef QList dataItemProviders_t(); + +QgsGeoNodeConnectionItem::QgsGeoNodeConnectionItem( QgsDataItem *parent, QString name, QString path, QgsGeoNodeConnection *conn ) + : QgsDataCollectionItem( parent, name, path ) + , mGeoNodeName( parent->name() ) + , mUri( conn->uri().uri() ) + , mConnection( conn ) +{ + mIconName = QStringLiteral( "mIconConnect.png" ); +} + +QVector QgsGeoNodeConnectionItem::createChildren() +{ + QVector services; + + QString url = mConnection->uri().param( "url" ); + QgsGeoNodeRequest geonodeRequest( url, true ); + + QStringList wmsUrl = geonodeRequest.serviceUrls( QStringLiteral( "WMS" ) ); + QStringList wfsUrl = geonodeRequest.serviceUrls( QStringLiteral( "WFS" ) ); + QStringList xyzUrl = geonodeRequest.serviceUrls( QStringLiteral( "XYZ" ) ); + + if ( !wmsUrl.isEmpty() ) + { + QString path = mPath + "/wms"; + QgsDataItem *service = new QgsGeoNodeServiceItem( this, mConnection, QStringLiteral( "WMS" ), path ); + services.append( service ); + } + + if ( !wfsUrl.isEmpty() ) + { + QString path = mPath + "/wfs"; + QgsDataItem *service = new QgsGeoNodeServiceItem( this, mConnection, QStringLiteral( "WFS" ), path ); + services.append( service ); + } + + if ( !xyzUrl.isEmpty() ) + { + QString path = mPath + "/xyz"; + QgsDataItem *service = new QgsGeoNodeServiceItem( this, mConnection, QStringLiteral( "XYZ" ), path ); + services.append( service ); + } + + return services; +} + +QList QgsGeoNodeConnectionItem::actions() +{ + QAction *actionEdit = new QAction( tr( "Edit..." ), this ); + QAction *actionDelete = new QAction( tr( "Delete" ), this ); + connect( actionEdit, &QAction::triggered, this, &QgsGeoNodeConnectionItem::editConnection ); + connect( actionDelete, &QAction::triggered, this, &QgsGeoNodeConnectionItem::deleteConnection ); + return QList() << actionEdit << actionDelete; +} + +void QgsGeoNodeConnectionItem::editConnection() +{ + QgsGeoNodeNewConnection *nc = new QgsGeoNodeNewConnection( nullptr, mConnection->connName() ); + nc->setWindowTitle( tr( "Modify GeoNode connection" ) ); + + if ( nc->exec() ) + { + // the parent should be updated + mParent->refresh(); + } +} + +QgsGeoNodeServiceItem::QgsGeoNodeServiceItem( QgsDataItem *parent, QgsGeoNodeConnection *conn, QString serviceName, QString path ) + : QgsDataCollectionItem( parent, serviceName, path ) + , mName( conn->connName() ) + , mServiceName( serviceName ) + , mConnection( conn ) +{ + if ( serviceName == QStringLiteral( "WMS" ) || serviceName == QStringLiteral( "XYZ" ) ) + { + mIconName = QStringLiteral( "mIconWms.svg" ); + } + else + { + mIconName = QStringLiteral( "mIconWfs.svg" ); + } +} + +QVector QgsGeoNodeServiceItem::createChildren() +{ + QVector children; + QHash serviceItems; // service/provider key + + int layerCount = 0; + // Try to open with service provider + bool skipProvider = false; + + QgsGeoNodeConnectionItem *parentItem = dynamic_cast( mParent ); + QString pathPrefix = parentItem->mGeoNodeName.toLower() + ":/"; + + while ( !skipProvider ) + { + const QString &key = mServiceName != QString( "WFS" ) ? QString( "WMS" ).toLower() : mServiceName; + std::unique_ptr< QLibrary > library( QgsProviderRegistry::instance()->createProviderLibrary( key ) ); + if ( !library ) + { + skipProvider = true; + continue; + } + + dataItemProviders_t *dataItemProvidersFn = reinterpret_cast< dataItemProviders_t * >( cast_to_fptr( library->resolve( "dataItemProviders" ) ) ); + dataItem_t *dItem = ( dataItem_t * ) cast_to_fptr( library->resolve( "dataItem" ) ); + if ( !dItem && !dataItemProvidersFn ) + { + skipProvider = true; + continue; + } + + QString path = pathPrefix + mName; + + QVector items; + Q_FOREACH ( QgsDataItemProvider *pr, dataItemProvidersFn() ) + { + items = pr->name().startsWith( mServiceName ) ? pr->createDataItems( path, this ) : items; + if ( !items.isEmpty() ) + { + break; + } + } + + if ( items.isEmpty() ) + { + skipProvider = true; + continue; + } + + if ( mServiceName == QStringLiteral( "XYZ" ) ) + { + return items; + } + + Q_FOREACH ( QgsDataItem *item, items ) + { + item->populate( true ); // populate in foreground - this is already run in a thread + + layerCount += item->rowCount(); + if ( item->rowCount() > 0 ) + { + serviceItems.insert( item, key ); + } + else + { + //delete item; + } + } + + skipProvider = true; + } + + Q_FOREACH ( QgsDataItem *item, serviceItems.keys() ) + { + QString providerKey = serviceItems.value( item ); + + // Add layers directly to service item + Q_FOREACH ( QgsDataItem *subItem, item->children() ) + { + if ( subItem->path().endsWith( QString( "error" ) ) ) + { + continue; + } + item->removeChildItem( subItem ); + subItem->setParent( this ); + replacePath( subItem, providerKey.toLower() + ":/", pathPrefix ); + children.append( subItem ); + } + + delete item; + } + + return children; +} + +// reset path recursively +void QgsGeoNodeServiceItem::replacePath( QgsDataItem *item, QString before, QString after ) +{ + item->setPath( item->path().replace( before, after ) ); + Q_FOREACH ( QgsDataItem *subItem, item->children() ) + { + replacePath( subItem, before, after ); + } +} + +QgsGeoNodeRootItem::QgsGeoNodeRootItem( QgsDataItem *parent, QString name, QString path ) : QgsDataCollectionItem( parent, name, path ) +{ + mCapabilities |= Fast; + { + mIconName = QStringLiteral( "mIconGeonode.svg" ); + } + populate(); +} + +QVector QgsGeoNodeRootItem::createChildren() +{ + QVector connections; + + Q_FOREACH ( const QString &connName, QgsGeoNodeConnection::connectionList() ) + { + QgsGeoNodeConnection *connection = nullptr; + connection = new QgsGeoNodeConnection( connName ); + QString path = mPath + "/" + connName; + QgsDataItem *conn = new QgsGeoNodeConnectionItem( this, connName, path, connection ); + connections.append( conn ); + } + return connections; +} + +QList QgsGeoNodeRootItem::actions() +{ + QAction *actionNew = new QAction( tr( "New Connection..." ), this ); + connect( actionNew, &QAction::triggered, this, &QgsGeoNodeRootItem::newConnection ); + return QList() << actionNew; +} + +void QgsGeoNodeRootItem::newConnection() +{ + QgsGeoNodeNewConnection *nc = new QgsGeoNodeNewConnection( nullptr ); + + if ( nc->exec() ) + { + refresh(); + } +} + + +QgsDataItem *QgsGeoNodeDataItemProvider::createDataItem( const QString &path, QgsDataItem *parentItem ) +{ + QgsDebugMsg( "thePath = " + path ); + if ( path.isEmpty() ) + { + return new QgsGeoNodeRootItem( parentItem, QStringLiteral( "GeoNode" ), QStringLiteral( "geonode:" ) ); + } + + // path schema: geonode:/connection name (used by OWS) + if ( path.startsWith( QLatin1String( "geonode:/" ) ) ) + { + QString connectionName = path.split( '/' ).last(); + if ( QgsGeoNodeConnection::connectionList().contains( connectionName ) ) + { + QgsGeoNodeConnection *connection = new QgsGeoNodeConnection( connectionName ); + return new QgsGeoNodeConnectionItem( parentItem, QStringLiteral( "GeoNode" ), path, connection ); + } + } + + return nullptr; +} diff --git a/src/providers/ows/qgsgeonodedataitems.h b/src/providers/ows/qgsgeonodedataitems.h new file mode 100644 index 000000000000..7d51b128f05e --- /dev/null +++ b/src/providers/ows/qgsgeonodedataitems.h @@ -0,0 +1,87 @@ +/*************************************************************************** + qgsgeonodedataitems.h + --------------------- + begin : Feb 2017 + copyright : (C) 2017 by Muhammad Yarjuna Rohmat, Ismail Sunni + email : rohmat at kartoza dot com, ismail at kartoza 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. * + * * + ***************************************************************************/ + +#ifndef QGSGEONODEDATAITEMS_H +#define QGSGEONODEDATAITEMS_H + +#include "qgsdataitem.h" +#include "qgsdataitemprovider.h" +#include "qgsdataprovider.h" +#include "qgsdatasourceuri.h" +#include "qgsgeonodeconnection.h" + +class QgsGeoNodeConnectionItem : public QgsDataCollectionItem +{ + Q_OBJECT + public: + QgsGeoNodeConnectionItem( QgsDataItem *parent, QString name, QString path, QgsGeoNodeConnection *conn ); + QVector createChildren() override; + virtual QList actions() override; + + QString mGeoNodeName; + + private: + void editConnection(); + void deleteConnection() + { + QgsGeoNodeConnection::deleteConnection( mParent->name() ); + mParent->refresh(); + }; + + QString mUri; + QgsGeoNodeConnection *mConnection = nullptr; +}; + +class QgsGeoNodeServiceItem : public QgsDataCollectionItem +{ + Q_OBJECT + public: + QgsGeoNodeServiceItem( QgsDataItem *parent, QgsGeoNodeConnection *conn, QString serviceName, QString path ); + QVector createChildren() override; + + private: + void replacePath( QgsDataItem *item, QString before, QString after ); + QString mName; + QString mServiceName; + QString mUri; + QgsGeoNodeConnection *mConnection = nullptr; +}; + +class QgsGeoNodeRootItem : public QgsDataCollectionItem +{ + Q_OBJECT + public: + QgsGeoNodeRootItem( QgsDataItem *parent, QString name, QString path ); + + QVector createChildren() override; + + virtual QList actions() override; + + private slots: + void newConnection(); +}; + +//! Provider for Geonode root data item +class QgsGeoNodeDataItemProvider : public QgsDataItemProvider +{ + public: + virtual QString name() override { return QStringLiteral( "GeoNode" ); } + + virtual int capabilities() override { return QgsDataProvider::Net; } + + virtual QgsDataItem *createDataItem( const QString &path, QgsDataItem *parentItem ) override; +}; + +#endif //QGSGEONODEDATAITEMS_H diff --git a/src/providers/ows/qgsowsdataitems.cpp b/src/providers/ows/qgsowsdataitems.cpp index c58302c79c42..1ed5b2b018a3 100644 --- a/src/providers/ows/qgsowsdataitems.cpp +++ b/src/providers/ows/qgsowsdataitems.cpp @@ -23,6 +23,9 @@ #include "qgsnewhttpconnection.h" #include "qgsowssourceselect.h" #endif +#include "qgsgeonodeconnection.h" +#include "qgsgeonodenewconnection.h" +#include "qgsgeonodedataitems.h" #include "qgsapplication.h" @@ -259,12 +262,14 @@ void QgsOWSRootItem::newConnection() static QStringList extensions = QStringList(); static QStringList wildcards = QStringList(); -QGISEXTERN int dataCapabilities() +QGISEXTERN QList dataItemProviders() { - return QgsDataProvider::Net; + return QList() + << new QgsOwsDataItemProvider + << new QgsGeoNodeDataItemProvider; } -QGISEXTERN QgsDataItem *dataItem( QString path, QgsDataItem *parentItem ) +QgsDataItem *QgsOwsDataItemProvider::createDataItem( const QString &path, QgsDataItem *parentItem ) { if ( path.isEmpty() ) { @@ -272,13 +277,3 @@ QGISEXTERN QgsDataItem *dataItem( QString path, QgsDataItem *parentItem ) } return nullptr; } - -//QGISEXTERN QgsOWSSourceSelect * selectWidget( QWidget * parent, Qt::WindowFlags fl ) -QGISEXTERN QDialog *selectWidget( QWidget *parent, Qt::WindowFlags fl, QgsProviderRegistry::WidgetMode widgetMode ) -{ - Q_UNUSED( parent ); - Q_UNUSED( fl ); - Q_UNUSED( widgetMode ); - //return new QgsOWSSourceSelect( parent, fl, widgetMode ); - return nullptr; -} diff --git a/src/providers/ows/qgsowsdataitems.h b/src/providers/ows/qgsowsdataitems.h index 05a07653dc0f..ac99d861150f 100644 --- a/src/providers/ows/qgsowsdataitems.h +++ b/src/providers/ows/qgsowsdataitems.h @@ -16,7 +16,11 @@ #define QGSOWSDATAITEMS_H #include "qgsdataitem.h" +#include "qgsdataitemprovider.h" +#include "qgsdataprovider.h" #include "qgsdatasourceuri.h" +#include "qgsgeonodeconnection.h" + class QgsOWSConnectionItem : public QgsDataCollectionItem { Q_OBJECT @@ -63,4 +67,15 @@ class QgsOWSRootItem : public QgsDataCollectionItem #endif }; +//! Provider for ows root data item +class QgsOwsDataItemProvider : public QgsDataItemProvider +{ + public: + virtual QString name() override { return QStringLiteral( "OWS" ); } + + virtual int capabilities() override { return QgsDataProvider::Net; } + + virtual QgsDataItem *createDataItem( const QString &path, QgsDataItem *parentItem ) override; +}; + #endif // QGSOWSDATAITEMS_H diff --git a/src/providers/wfs/CMakeLists.txt b/src/providers/wfs/CMakeLists.txt index 328a118ffdbe..370b808b6e59 100644 --- a/src/providers/wfs/CMakeLists.txt +++ b/src/providers/wfs/CMakeLists.txt @@ -49,6 +49,7 @@ INCLUDE_DIRECTORIES ( ${CMAKE_SOURCE_DIR}/src/core/expression ${CMAKE_SOURCE_DIR}/src/core/geometry ${CMAKE_SOURCE_DIR}/src/core/symbology # needed by qgsvectorfilewriter.h + ${CMAKE_SOURCE_DIR}/src/core/geonode ${CMAKE_SOURCE_DIR}/src/core/metadata ${CMAKE_SOURCE_DIR}/src/gui ${CMAKE_SOURCE_DIR}/src/gui/auth diff --git a/src/providers/wfs/qgswfsdataitems.cpp b/src/providers/wfs/qgswfsdataitems.cpp index e04fa4631790..cfcbdce2111f 100644 --- a/src/providers/wfs/qgswfsdataitems.cpp +++ b/src/providers/wfs/qgswfsdataitems.cpp @@ -12,6 +12,7 @@ * (at your option) any later version. * * * ***************************************************************************/ +#include "qgsdataitemprovider.h" #include "qgsdataprovider.h" #include "qgslogger.h" #include "qgswfsconstants.h" @@ -20,6 +21,8 @@ #include "qgswfsdataitems.h" #include "qgswfsdatasourceuri.h" #include "qgssettings.h" +#include "qgsgeonodeconnection.h" +#include "qgsgeonoderequest.h" #ifdef HAVE_GUI #include "qgsnewhttpconnection.h" @@ -192,6 +195,84 @@ void QgsWfsRootItem::newConnection() } #endif + +////// + + +QgsDataItem *QgsWfsDataItemProvider::createDataItem( const QString &path, QgsDataItem *parentItem ) +{ + QgsDebugMsg( "thePath = " + path ); + if ( path.isEmpty() ) + { + return new QgsWfsRootItem( parentItem, QStringLiteral( "WFS" ), QStringLiteral( "wfs:" ) ); + } + + // path schema: wfs:/connection name (used by OWS) + if ( path.startsWith( QLatin1String( "wfs:/" ) ) ) + { + QString connectionName = path.split( '/' ).last(); + if ( QgsWfsConnection::connectionList().contains( connectionName ) ) + { + QgsWfsConnection connection( connectionName ); + return new QgsWfsConnectionItem( parentItem, QStringLiteral( "WFS" ), path, connection.uri().uri() ); + } + } + else if ( path.startsWith( QLatin1String( "geonode:/" ) ) ) + { + QString connectionName = path.split( '/' ).last(); + if ( QgsGeoNodeConnection::connectionList().contains( connectionName ) ) + { + QgsGeoNodeConnection connection( connectionName ); + + QString url = connection.uri().param( "url" ); + QgsGeoNodeRequest geonodeRequest( url, true ); + + QgsWFSDataSourceURI sourceUri( geonodeRequest.serviceUrls( QStringLiteral( "WFS" ) )[0] ); + + QgsDebugMsg( QString( "WFS full uri: '%1'." ).arg( QString( sourceUri.uri() ) ) ); + + return new QgsWfsConnectionItem( parentItem, QStringLiteral( "WFS" ), path, sourceUri.uri() ); + } + } + + return nullptr; +} + +QVector QgsWfsDataItemProvider::createDataItems( const QString &path, QgsDataItem *parentItem ) +{ + QVector items; + if ( path.startsWith( QLatin1String( "geonode:/" ) ) ) + { + QString connectionName = path.split( '/' ).last(); + if ( QgsGeoNodeConnection::connectionList().contains( connectionName ) ) + { + QgsGeoNodeConnection connection( connectionName ); + + QString url = connection.uri().param( "url" ); + QgsGeoNodeRequest geonodeRequest( url, true ); + + QStringList encodedUris( geonodeRequest.serviceUrls( QStringLiteral( "WFS" ) ) ); + + if ( !encodedUris.isEmpty() ) + { + Q_FOREACH ( QString encodedUri, encodedUris ) + { + QgsWFSDataSourceURI uri( encodedUri ); + QgsDebugMsg( QString( "WFS full uri: '%1'." ).arg( QString( uri.uri() ) ) ); + + QgsDataItem *item = new QgsWfsConnectionItem( parentItem, QStringLiteral( "WFS" ), path, uri.uri() ); + if ( item ) + { + items.append( item ); + } + } + } + } + } + + return items; +} + // --------------------------------------------------------------------------- #ifdef HAVE_GUI @@ -227,3 +308,9 @@ QGISEXTERN QgsDataItem *dataItem( QString path, QgsDataItem *parentItem ) return nullptr; } + +QGISEXTERN QList dataItemProviders() +{ + return QList() + << new QgsWfsDataItemProvider; +} diff --git a/src/providers/wfs/qgswfsdataitems.h b/src/providers/wfs/qgswfsdataitems.h index 286cef478697..85ce3cba506a 100644 --- a/src/providers/wfs/qgswfsdataitems.h +++ b/src/providers/wfs/qgswfsdataitems.h @@ -16,6 +16,8 @@ #define QGSWFSDATAITEMS_H #include "qgsdataitem.h" +#include "qgsdataitemprovider.h" +#include "qgsdataprovider.h" #include "qgsdatasourceuri.h" #include "qgswfscapabilities.h" @@ -80,4 +82,18 @@ class QgsWfsLayerItem : public QgsLayerItem }; +//! Provider for WFS root data item +class QgsWfsDataItemProvider : public QgsDataItemProvider +{ + public: + virtual QString name() override { return QStringLiteral( "WFS" ); } + + virtual int capabilities() override { return QgsDataProvider::Net; } + + virtual QgsDataItem *createDataItem( const QString &path, QgsDataItem *parentItem ) override; + + virtual QVector createDataItems( const QString &path, QgsDataItem *parentItem ) override; +}; + + #endif // QGSWFSDATAITEMS_H diff --git a/src/providers/wms/CMakeLists.txt b/src/providers/wms/CMakeLists.txt index 0a00cecd006b..98a884888676 100644 --- a/src/providers/wms/CMakeLists.txt +++ b/src/providers/wms/CMakeLists.txt @@ -38,6 +38,7 @@ INCLUDE_DIRECTORIES( ${CMAKE_SOURCE_DIR}/src/core/auth ${CMAKE_SOURCE_DIR}/src/core/expression ${CMAKE_SOURCE_DIR}/src/core/geometry + ${CMAKE_SOURCE_DIR}/src/core/geonode ${CMAKE_SOURCE_DIR}/src/core/raster ${CMAKE_SOURCE_DIR}/src/core/metadata ${CMAKE_SOURCE_DIR}/src/gui diff --git a/src/providers/wms/qgswmsdataitems.cpp b/src/providers/wms/qgswmsdataitems.cpp index d44c11150e80..fee211cb4d31 100644 --- a/src/providers/wms/qgswmsdataitems.cpp +++ b/src/providers/wms/qgswmsdataitems.cpp @@ -28,6 +28,9 @@ #include "qgstilescalewidget.h" #include "qgsxyzconnectiondialog.h" #endif +#include "qgsgeonodeconnection.h" +#include "qgsgeonoderequest.h" +#include "qgssettings.h" #include @@ -565,3 +568,89 @@ void QgsXyzLayerItem::deleteConnection() mParent->refresh(); } #endif + +// --------------------------------------------------------------------------- + + +QVector QgsWmsDataItemProvider::createDataItems( const QString &path, QgsDataItem *parentItem ) +{ + QVector items; + if ( path.startsWith( QLatin1String( "geonode:/" ) ) ) + { + QString connectionName = path.split( '/' ).last(); + if ( QgsGeoNodeConnection::connectionList().contains( connectionName ) ) + { + QgsGeoNodeConnection connection( connectionName ); + + QString url = connection.uri().param( "url" ); + QgsGeoNodeRequest geonodeRequest( url, true ); + + QStringList encodedUris( geonodeRequest.serviceUrls( QStringLiteral( "WMS" ) ) ); + + if ( !encodedUris.isEmpty() ) + { + Q_FOREACH ( QString encodedUri, encodedUris ) + { + QgsDebugMsg( encodedUri ); + QgsDataSourceUri uri; + QgsSettings settings; + QString key( connection.pathGeoNodeConnection() + "/" + connectionName ); + + QString dpiMode = settings.value( key + "/wms/dpiMode", "all", QgsSettings::Providers ).toString(); + uri.setParam( QStringLiteral( "url" ), encodedUri ); + if ( !dpiMode.isEmpty() ) + { + uri.setParam( QStringLiteral( "dpiMode" ), dpiMode ); + } + + QgsDebugMsg( QString( "WMS full uri: '%1'." ).arg( QString( uri.encodedUri() ) ) ); + + QgsDataItem *item = new QgsWMSConnectionItem( parentItem, QStringLiteral( "WMS" ), path, uri.encodedUri() ); + if ( item ) + { + items.append( item ); + } + } + } + } + } + + return items; +} + +QVector QgsXyzTileDataItemProvider::createDataItems( const QString &path, QgsDataItem *parentItem ) +{ + QVector items; + if ( path.startsWith( QLatin1String( "geonode:/" ) ) ) + { + QString connectionName = path.split( '/' ).last(); + if ( QgsGeoNodeConnection::connectionList().contains( connectionName ) ) + { + QgsGeoNodeConnection connection( connectionName ); + + QString url = connection.uri().param( "url" ); + QgsGeoNodeRequest geonodeRequest( url, true ); + + QgsStringMap urlData( geonodeRequest.serviceUrlData( QStringLiteral( "XYZ" ) ) ); + + if ( !urlData.isEmpty() ) + { + Q_FOREACH ( QString layerName, urlData.keys() ) + { + QgsDebugMsg( urlData[ layerName] ); + QgsDataSourceUri uri; + uri.setParam( QStringLiteral( "type" ), QStringLiteral( "xyz" ) ); + uri.setParam( QStringLiteral( "url" ), urlData[ layerName ] ); + + QgsDataItem *item = new QgsXyzLayerItem( parentItem, layerName, path, uri.encodedUri() ); + if ( item ) + { + items.append( item ); + } + } + } + } + } + + return items; +} diff --git a/src/providers/wms/qgswmsdataitems.h b/src/providers/wms/qgswmsdataitems.h index facd17c9fdb7..9293b6394019 100644 --- a/src/providers/wms/qgswmsdataitems.h +++ b/src/providers/wms/qgswmsdataitems.h @@ -19,6 +19,7 @@ #include "qgsdataitemprovider.h" #include "qgsdatasourceuri.h" #include "qgswmsprovider.h" +#include "qgsgeonodeconnection.h" class QgsWmsCapabilitiesDownload; @@ -122,6 +123,8 @@ class QgsWmsDataItemProvider : public QgsDataItemProvider virtual int capabilities() override { return QgsDataProvider::Net; } virtual QgsDataItem *createDataItem( const QString &path, QgsDataItem *parentItem ) override; + + virtual QVector createDataItems( const QString &path, QgsDataItem *parentItem ) override; }; @@ -177,6 +180,8 @@ class QgsXyzTileDataItemProvider : public QgsDataItemProvider return new QgsXyzTileRootItem( parentItem, QStringLiteral( "XYZ Tiles" ), QStringLiteral( "xyz:" ) ); return nullptr; } + + virtual QVector createDataItems( const QString &path, QgsDataItem *parentItem ) override; }; diff --git a/src/ui/qgsgeonodesourceselectbase.ui b/src/ui/qgsgeonodesourceselectbase.ui new file mode 100644 index 000000000000..e931c65f4fed --- /dev/null +++ b/src/ui/qgsgeonodesourceselectbase.ui @@ -0,0 +1,217 @@ + + + QgsGeonodeSourceSelectBase + + + + 0 + 0 + 592 + 439 + + + + Add Geonode Layer + + + + + + Geonode connections + + + + + + + + + + + false + + + Connect to selected service + + + C&onnect + + + + + + + Create a new service connection + + + &New + + + + + + + false + + + Edit selected service connection + + + Edit + + + + + + + false + + + Remove connection to selected service + + + Remove + + + + + + + Qt::Horizontal + + + QSizePolicy::Expanding + + + + 171 + 30 + + + + + + + + Load connections from file + + + Load + + + + + + + Save connections to file + + + Save + + + + + + + + + + + + QAbstractItemView::NoEditTriggers + + + true + + + QAbstractItemView::ExtendedSelection + + + true + + + true + + + + + + + + + Use title for layer name + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + true + + + Filter + + + lineFilter + + + + + + + true + + + Display WFS FeatureTypes containing this word in the title, name or abstract + + + Display WFS FeatureTypes containing this word in the title, name or abstract + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Close|QDialogButtonBox::Help + + + + + + + cmbConnections + btnConnect + btnNew + btnEdit + btnDelete + btnLoad + btnSave + lineFilter + treeView + cbxUseTitleLayerName + + + + diff --git a/src/ui/qgsnewgeonodeconnectionbase.ui b/src/ui/qgsnewgeonodeconnectionbase.ui new file mode 100644 index 000000000000..437f7f8201a6 --- /dev/null +++ b/src/ui/qgsnewgeonodeconnectionbase.ui @@ -0,0 +1,355 @@ + + + QgsNewGeoNodeConnectionBase + + + + 0 + 0 + 448 + 475 + + + + Create a new Geonode connection + + + true + + + true + + + + + + Connection details + + + + + + WFS Options + + + + + + Version + + + + + + + <html><head/><body><p>Select protocol version</p></body></html> + + + + + + + Max. number of features + + + + + + + <html><head/><body><p>Enter a number to limit the maximum number of features retrieved in a single GetFeature request. If let to empty, server default will apply.</p></body></html> + + + + + + + + + + &Test Connection + + + + + + + Ignore axis orientation (WFS 1.1/WFS 2.0) + + + + + + + + 0 + 0 + + + + 0 + + + + Authentication + + + + + + + 0 + 0 + + + + If the service requires basic authentication, enter a user name and optional password + + + Qt::PlainText + + + true + + + + + + + &User name + + + txtUserName + + + + + + + + + + Password + + + txtPassword + + + + + + + QLineEdit::Password + + + + + + + Qt::Vertical + + + + 0 + 0 + + + + + + + + + + + + Ignore axis orientation (WMS 1.3/WMTS) + + + + + + + Invert axis orientation + + + + + + + WMS Options + + + + + + + + + Referer + + + txtReferer + + + + + + + + + + DPI-Mode + + + cmbDpiMode + + + + + + + + + + Invert axis orientation + + + + + + + Ignore GetFeatureInfo URI reported in capabilities + + + + + + + + + Name + + + true + + + 5 + + + txtName + + + + + + + + 0 + 0 + + + + Name of the new connection + + + true + + + + + + + URL + + + 5 + + + txtUrl + + + + + + + HTTP address of the Web Map Server + + + + + + + + + Ignore GetMap/GetTile URI reported in capabilities + + + + + + + Smooth pixmap transform + + + + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Help|QDialogButtonBox::Ok + + + + + + + + + QgsPasswordLineEdit + QLineEdit +
qgspasswordlineedit.h
+
+
+ + tabAuth + txtUserName + txtPassword + + + + + buttonBox + accepted() + QgsNewGeoNodeConnectionBase + accept() + + + 421 + 453 + + + 430 + 98 + + + + + buttonBox + rejected() + QgsNewGeoNodeConnectionBase + reject() + + + 330 + 453 + + + 426 + 38 + + + + +
diff --git a/src/ui/qgsnewhttpconnectionbase.ui b/src/ui/qgsnewhttpconnectionbase.ui index 1e1adad5106b..d7b895be73af 100644 --- a/src/ui/qgsnewhttpconnectionbase.ui +++ b/src/ui/qgsnewhttpconnectionbase.ui @@ -229,7 +229,7 @@ - + Version diff --git a/tests/src/core/CMakeLists.txt b/tests/src/core/CMakeLists.txt index de87bf719f25..b9117ffa3a37 100755 --- a/tests/src/core/CMakeLists.txt +++ b/tests/src/core/CMakeLists.txt @@ -14,6 +14,7 @@ INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_SOURCE_DIR}/src/core/expression ${CMAKE_SOURCE_DIR}/src/core/geometry ${CMAKE_SOURCE_DIR}/src/core/layout + ${CMAKE_SOURCE_DIR}/src/core/geonode ${CMAKE_SOURCE_DIR}/src/core/metadata ${CMAKE_SOURCE_DIR}/src/core/effects ${CMAKE_SOURCE_DIR}/src/core/layertree @@ -117,6 +118,7 @@ SET(TESTS testqgsgeometryimport.cpp testqgsgeometry.cpp testqgsgeometryutils.cpp + testqgsgeonodeconnection.cpp testqgsgml.cpp testqgsgradients.cpp testqgsgraduatedsymbolrenderer.cpp diff --git a/tests/src/core/testqgsgeonodeconnection.cpp b/tests/src/core/testqgsgeonodeconnection.cpp new file mode 100644 index 000000000000..2ae016f3d4a7 --- /dev/null +++ b/tests/src/core/testqgsgeonodeconnection.cpp @@ -0,0 +1,126 @@ +/*************************************************************************** + testqgsgeonodeconnection.cpp + -------------------------------------- + Date : Saturday, 25 March 2017 + Copyright: (C) 2017 + Email: ismail@kartoza.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 +#include +#include + +//#include "qgis_core.h" +#include "qgsgeonodeconnection.h" +#include "qgssettings.h" + +/** \ingroup UnitTests + * This is a unit test for the QgsGeoConnection class. + */ + +class TestQgsGeoNodeConnection: public QObject +{ + Q_OBJECT + + private slots: + // will be called before the first testfunction is executed. + void initTestCase(); + // will be called after the last testfunction was executed. + void cleanupTestCase() + { + QgsGeoNodeConnection::deleteConnection( mGeoNodeConnectionName ); + QgsGeoNodeConnection::deleteConnection( mDemoGeoNodeName ); + QgsGeoNodeConnection::deleteConnection( mKartozaGeoNodeQGISServerName ); + } + // will be called before each testfunction is executed. + void init() {} + // will be called after every testfunction. + void cleanup() {} + + // Check if we can create geonode connection from database. + void testCreation(); + + private: + QString mGeoNodeConnectionName; + QString mGeoNodeConnectionURL; + + QString mDemoGeoNodeName; + QString mDemoGeoNodeURL; + + QString mKartozaGeoNodeQGISServerName; + QString mKartozaGeoNodeQGISServerURL; + + QString mKartozaGeoNodeGeoServerName; + QString mKartozaGeoNodeGeoServerURL; + + bool mSkipRemoteTest; +}; + +// Runs before all unit tests +void TestQgsGeoNodeConnection::initTestCase() +{ + std::cout << "CTEST_FULL_OUTPUT" << std::endl; + mGeoNodeConnectionName = QStringLiteral( "ThisIsAGeoNodeConnection" ); + mGeoNodeConnectionURL = QStringLiteral( "www.thisisageonodeurl.com" ); + mDemoGeoNodeName = QStringLiteral( "Demo GeoNode" ); + mDemoGeoNodeURL = QStringLiteral( "demo.geonode.org" ); + mKartozaGeoNodeQGISServerName = QStringLiteral( "Staging Kartoza GeoNode QGIS Server" ); + mKartozaGeoNodeQGISServerURL = QStringLiteral( "staging.geonode.kartoza.com" ); + mKartozaGeoNodeGeoServerName = QStringLiteral( "Staging Kartoza GeoNode GeoServer" ); + mKartozaGeoNodeGeoServerURL = QStringLiteral( "staginggs.geonode.kartoza.com" ); + + // Change it to skip remote testing + mSkipRemoteTest = true; + + // Add Demo GeoNode Connection + QgsSettings settings; + + // Testing real server, demo.geonode.org. Need to be changed later. + settings.setValue( QgsGeoNodeConnection::pathGeoNodeConnection() + QStringLiteral( "/%1/url" ).arg( mDemoGeoNodeName ), mDemoGeoNodeURL, QgsSettings::Providers ); + // Testing real server, staging.geonode.kartoza.com. Need to be changed later. + settings.setValue( QgsGeoNodeConnection::pathGeoNodeConnection() + QStringLiteral( "/%1/url" ).arg( mKartozaGeoNodeQGISServerName ), mKartozaGeoNodeQGISServerURL, QgsSettings::Providers ); + // Testing real server, staginggs.geonode.kartoza.com. Need to be changed later. + settings.setValue( QgsGeoNodeConnection::pathGeoNodeConnection() + QStringLiteral( "/%1/url" ).arg( mKartozaGeoNodeGeoServerName ), mKartozaGeoNodeGeoServerURL, QgsSettings::Providers ); +} + +// Test the creation of geonode connection +void TestQgsGeoNodeConnection::testCreation() +{ + if ( mSkipRemoteTest ) + { + QSKIP( "Skip remote test for faster testing" ); + } + + QStringList connectionList = QgsGeoNodeConnection::connectionList(); + int numberOfConnection = connectionList.count(); + // Verify if the demo.geonode.org is created properly + QVERIFY( connectionList.contains( mDemoGeoNodeName ) ); + QVERIFY( !connectionList.contains( mGeoNodeConnectionName ) ); + + // Add new GeoNode Connection + QgsSettings settings; + + settings.setValue( QgsGeoNodeConnection::pathGeoNodeConnection() + QStringLiteral( "/%1/url" ).arg( mGeoNodeConnectionName ), mGeoNodeConnectionURL, QgsSettings::Providers ); + + QStringList newConnectionList = QgsGeoNodeConnection::connectionList(); + int newNumberOfConnection = newConnectionList.count(); + + // Check the number is increased by 1 + QCOMPARE( numberOfConnection + 1, newNumberOfConnection ); + + // Verify if the new connection is created properly + QVERIFY( newConnectionList.contains( mGeoNodeConnectionName ) ); +} + +QGSTEST_MAIN( TestQgsGeoNodeConnection ) +#include "testqgsgeonodeconnection.moc"