Skip to content

Commit

Permalink
[FEATURE][API] Allow plugins to register custom "Project Open" handlers
Browse files Browse the repository at this point in the history
These allow plugins to extend the "Open Project" dialog by adding in support
for new file filters, which appear in the formats drop down list alongside
the existing "QGS Projects" entry.

Custom project open handlers then get first chance at loading project files.

This allows plugins to extend QGIS support by adding integrated support for
opening projects from non QGS/QGZ formats, e.g. allowing users to open
ArcGIS MXD documents or MapInfo WOR Workspaces direct from the project open
dialog.

These non-native projects are also added to the recent projects list and
welcome screen, giving them a truly first-class experience within QGIS.

Sponsored by SLYR
  • Loading branch information
nyalldawson committed Apr 6, 2020
1 parent 8fa6338 commit 2716415
Show file tree
Hide file tree
Showing 11 changed files with 275 additions and 14 deletions.
35 changes: 31 additions & 4 deletions python/gui/auto_generated/qgisinterface.sip.in
Expand Up @@ -1157,11 +1157,11 @@ Unregister a previously registered tool factory from the development/debugging t

virtual void registerCustomDropHandler( QgsCustomDropHandler *handler ) = 0;
%Docstring
Register a new custom drop handler.
Register a new custom drop ``handler``.

.. note::

Ownership of the factory is not transferred, and the factory must
Ownership of ``handler`` is not transferred, and the handler must
be unregistered when plugin is unloaded.

.. seealso:: :py:class:`QgsCustomDropHandler`
Expand All @@ -1173,13 +1173,40 @@ Register a new custom drop handler.

virtual void unregisterCustomDropHandler( QgsCustomDropHandler *handler ) = 0;
%Docstring
Unregister a previously registered custom drop handler.
Unregister a previously registered custom drop ``handler``.

.. seealso:: :py:class:`QgsCustomDropHandler`

.. seealso:: :py:func:`registerCustomDropHandler`

.. versionadded:: 3.0
%End

virtual void registerCustomProjectOpenHandler( QgsCustomProjectOpenHandler *handler ) = 0;
%Docstring
Register a new custom project open ``handler``.

.. note::

Ownership of ``handler`` is not transferred, and the handler must
be unregistered when plugin is unloaded.

.. seealso:: :py:class:`QgsCustomProjectOpenHandler`

.. seealso:: :py:func:`unregisterCustomProjectOpenHandler`

.. versionadded:: 3.14
%End

virtual void unregisterCustomProjectOpenHandler( QgsCustomProjectOpenHandler *handler ) = 0;
%Docstring
Unregister a previously registered custom project open ``handler``.

.. seealso:: :py:class:`QgsCustomDropHandler`

.. seealso:: :py:func:`registerCustomProjectOpenHandler`

.. versionadded:: 3.14
%End

virtual void registerCustomLayoutDropHandler( QgsLayoutCustomDropHandler *handler ) = 0;
Expand All @@ -1188,7 +1215,7 @@ Register a new custom drop ``handler`` for layout windows.

.. note::

Ownership of the factory is not transferred, and the factory must
Ownership of ``handler`` is not transferred, and the handler must
be unregistered when plugin is unloaded.

.. seealso:: :py:class:`QgsLayoutCustomDropHandler`
Expand Down
58 changes: 58 additions & 0 deletions python/gui/auto_generated/qgscustomprojectopenhandler.sip.in
@@ -0,0 +1,58 @@
/************************************************************************
* This file has been generated automatically from *
* *
* src/gui/qgscustomprojectopenhandler.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/



class QgsCustomProjectOpenHandler : QObject
{
%Docstring
Abstract base class that may be implemented to handle new project file types within
the QGIS application.

This interface allows extending the QGIS interface by adding support for opening additional
(non QGS/QGZ) project files, e.g. allowing plugins to add support for opening other
vendor project formats (such as ArcGIS MXD documents or MapInfo WOR workspaces).

Handler implementations should indicate the file types they support via their filters()
implementation, and then implement handleProjectOpen() to open the associated files.

.. versionadded:: 3.14
%End

%TypeHeaderCode
#include "qgscustomprojectopenhandler.h"
%End
public:

virtual bool handleProjectOpen( const QString &file ) = 0;
%Docstring
Called when the specified project ``file`` has been opened within QGIS. If ``True``
is returned, then the handler has accepted this file and it should not
be further processed (e.g. by other QgsCustomProjectOpenHandler).

It it is the subclasses' responsiblity to ignore file types it cannot handle
by returning ``False`` for these.

The base class implementation does nothing.
%End

virtual QStringList filters() const = 0;
%Docstring
Returns file filters associated with this handler, e.g. "MXD Documents (*.mxd)", "MapInfo Workspaces (*.wor)".

Each individual filter should be reflected as one entry in the returned list.
%End
};

/************************************************************************
* This file has been generated automatically from *
* *
* src/gui/qgscustomprojectopenhandler.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/
1 change: 1 addition & 0 deletions python/gui/gui_auto.sip
Expand Up @@ -54,6 +54,7 @@
%Include auto_generated/qgscredentialdialog.sip
%Include auto_generated/qgscurveeditorwidget.sip
%Include auto_generated/qgscustomdrophandler.sip
%Include auto_generated/qgscustomprojectopenhandler.sip
%Include auto_generated/qgsdatabaseschemacombobox.sip
%Include auto_generated/qgsdatabasetablecombobox.sip
%Include auto_generated/qgsdataitemguiprovider.sip
Expand Down
57 changes: 51 additions & 6 deletions src/app/qgisapp.cpp
Expand Up @@ -183,6 +183,7 @@ Q_GUI_EXPORT extern int qt_defaultDpiX();
#include "qgscoordinateutils.h"
#include "qgscredentialdialog.h"
#include "qgscustomdrophandler.h"
#include "qgscustomprojectopenhandler.h"
#include "qgscustomization.h"
#include "qgscustomlayerorderwidget.h"
#include "qgscustomprojectiondialog.h"
Expand Down Expand Up @@ -1965,6 +1966,17 @@ void QgisApp::unregisterCustomDropHandler( QgsCustomDropHandler *handler )
}
}

void QgisApp::registerCustomProjectOpenHandler( QgsCustomProjectOpenHandler *handler )
{
if ( !mCustomProjectOpenHandlers.contains( handler ) )
mCustomProjectOpenHandlers << handler;
}

void QgisApp::unregisterCustomProjectOpenHandler( QgsCustomProjectOpenHandler *handler )
{
mCustomProjectOpenHandlers.removeOne( handler );
}

QVector<QPointer<QgsCustomDropHandler> > QgisApp::customDropHandlers() const
{
return mCustomDropHandlers;
Expand Down Expand Up @@ -6581,18 +6593,40 @@ void QgisApp::fileOpen()
// Retrieve last used project dir from persistent settings
QgsSettings settings;
QString lastUsedDir = settings.value( QStringLiteral( "UI/lastProjectDir" ), QDir::homePath() ).toString();


QStringList fileFilters;
QStringList extensions;
fileFilters << tr( "QGIS files" ) + QStringLiteral( " (*.qgs *.qgz *.QGS *.QGZ)" );
extensions << QStringLiteral( "qgs" ) << QStringLiteral( "qgz" );
for ( QgsCustomProjectOpenHandler *handler : qgis::as_const( mCustomProjectOpenHandlers ) )
{
if ( handler )
{
const QStringList filters = handler->filters();
fileFilters.append( filters );
for ( const QString &filter : filters )
extensions.append( QgsFileUtils::extensionsFromFilter( filter ) );
}
}

// generate master "all projects" extension list
QString allEntry = tr( "All Project Files" ) + QStringLiteral( " (" );
for ( const QString &extension : extensions )
allEntry += QStringLiteral( "*.%1 *.%2 " ).arg( extension.toLower(), extension.toUpper() );
allEntry.chop( 1 ); // remove trailing ' '
allEntry += ')';
fileFilters.insert( 0, allEntry );

QString fullPath = QFileDialog::getOpenFileName( this,
tr( "Choose a QGIS Project File to Open" ),
tr( "Open Project" ),
lastUsedDir,
tr( "QGIS files" ) + " (*.qgs *.qgz *.QGS)" );
fileFilters.join( QStringLiteral( ";;" ) ) );
if ( fullPath.isNull() )
{
return;
}

// Fix by Tim - getting the dirPath from the dialog
// directly truncates the last node in the dir path.
// This is a workaround for that
QFileInfo myFI( fullPath );
QString myPath = myFI.path();
// Persist last used project dir
Expand Down Expand Up @@ -6647,7 +6681,18 @@ bool QgisApp::addProject( const QString &projectFile )
bool autoSetupOnFirstLayer = mLayerTreeCanvasBridge->autoSetupOnFirstLayer();
mLayerTreeCanvasBridge->setAutoSetupOnFirstLayer( false );

if ( !QgsProject::instance()->read( projectFile ) && !QgsZipUtils::isZipFile( projectFile ) )
// give custom handlers a chance first
bool handled = false;
for ( QgsCustomProjectOpenHandler *handler : qgis::as_const( mCustomProjectOpenHandlers ) )
{
if ( handler && handler->handleProjectOpen( projectFile ) )
{
handled = true;
break;
}
}

if ( !handled && !QgsProject::instance()->read( projectFile ) && !QgsZipUtils::isZipFile( projectFile ) )
{
QString backupFile = projectFile + "~";
QString loadBackupPrompt;
Expand Down
18 changes: 18 additions & 0 deletions src/app/qgisapp.h
Expand Up @@ -50,6 +50,7 @@ class QgsComposerManager;
class QgsContrastEnhancement;
class QgsCoordinateReferenceSystem;
class QgsCustomDropHandler;
class QgsCustomProjectOpenHandler;
class QgsCustomLayerOrderWidget;
class QgsDockWidget;
class QgsDoubleSpinBox;
Expand Down Expand Up @@ -701,6 +702,22 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow
//! Unregister a previously registered custom drop handler.
void unregisterCustomDropHandler( QgsCustomDropHandler *handler );

/**
* Register a new custom project open \a handler.
* \note Ownership of \a handler is not transferred, and the handler must
* be unregistered when plugin is unloaded.
* \see QgsCustomProjectOpenHandler
* \see unregisterCustomProjectOpenHandler()
*/
void registerCustomProjectOpenHandler( QgsCustomProjectOpenHandler *handler );

/**
* Unregister a previously registered custom project open \a handler.
* \see QgsCustomDropHandler
* \see registerCustomProjectOpenHandler()
*/
void unregisterCustomProjectOpenHandler( QgsCustomProjectOpenHandler *handler );

//! Returns a list of registered custom drop handlers.
QVector<QPointer<QgsCustomDropHandler >> customDropHandlers() const;

Expand Down Expand Up @@ -2399,6 +2416,7 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow
QList<QgsDevToolWidgetFactory * > mDevToolFactories;

QVector<QPointer<QgsCustomDropHandler>> mCustomDropHandlers;
QVector<QPointer<QgsCustomProjectOpenHandler>> mCustomProjectOpenHandlers;
QVector<QPointer<QgsLayoutCustomDropHandler>> mCustomLayoutDropHandlers;

QgsLayoutCustomDropHandler *mLayoutQptDropHandler = nullptr;
Expand Down
10 changes: 10 additions & 0 deletions src/app/qgisappinterface.cpp
Expand Up @@ -566,6 +566,16 @@ void QgisAppInterface::unregisterCustomDropHandler( QgsCustomDropHandler *handle
qgis->unregisterCustomDropHandler( handler );
}

void QgisAppInterface::registerCustomProjectOpenHandler( QgsCustomProjectOpenHandler *handler )
{
qgis->registerCustomProjectOpenHandler( handler );
}

void QgisAppInterface::unregisterCustomProjectOpenHandler( QgsCustomProjectOpenHandler *handler )
{
qgis->unregisterCustomProjectOpenHandler( handler );
}

QMenu *QgisAppInterface::projectMenu() { return qgis->projectMenu(); }
QMenu *QgisAppInterface::editMenu() { return qgis->editMenu(); }
QMenu *QgisAppInterface::viewMenu() { return qgis->viewMenu(); }
Expand Down
2 changes: 2 additions & 0 deletions src/app/qgisappinterface.h
Expand Up @@ -147,6 +147,8 @@ class APP_EXPORT QgisAppInterface : public QgisInterface
void unregisterDevToolWidgetFactory( QgsDevToolWidgetFactory *factory ) override;
void registerCustomDropHandler( QgsCustomDropHandler *handler ) override;
void unregisterCustomDropHandler( QgsCustomDropHandler *handler ) override;
void registerCustomProjectOpenHandler( QgsCustomProjectOpenHandler *handler ) override;
void unregisterCustomProjectOpenHandler( QgsCustomProjectOpenHandler *handler ) override;
void registerCustomLayoutDropHandler( QgsLayoutCustomDropHandler *handler ) override;
void unregisterCustomLayoutDropHandler( QgsLayoutCustomDropHandler *handler ) override;
QMenu *projectMenu() override;
Expand Down
2 changes: 2 additions & 0 deletions src/gui/CMakeLists.txt
Expand Up @@ -370,6 +370,7 @@ SET(QGIS_GUI_SRCS
qgscoordinateoperationwidget.cpp
qgscredentialdialog.cpp
qgscustomdrophandler.cpp
qgscustomprojectopenhandler.cpp
qgscurveeditorwidget.cpp
qgsdatabaseschemacombobox.cpp
qgsdatabasetablecombobox.cpp
Expand Down Expand Up @@ -592,6 +593,7 @@ SET(QGIS_GUI_HDRS
qgscredentialdialog.h
qgscurveeditorwidget.h
qgscustomdrophandler.h
qgscustomprojectopenhandler.h
qgsdatabaseschemacombobox.h
qgsdatabasetablecombobox.h
qgsdataitemguiprovider.h
Expand Down
27 changes: 23 additions & 4 deletions src/gui/qgisinterface.h
Expand Up @@ -39,6 +39,7 @@ class QWidget;
class QgsAdvancedDigitizingDockWidget;
class QgsAttributeDialog;
class QgsCustomDropHandler;
class QgsCustomProjectOpenHandler;
class QgsLayoutCustomDropHandler;
class QgsFeature;
class QgsLayerTreeMapCanvasBridge;
Expand Down Expand Up @@ -957,8 +958,8 @@ class GUI_EXPORT QgisInterface : public QObject
virtual void unregisterDevToolWidgetFactory( QgsDevToolWidgetFactory *factory ) = 0;

/**
* Register a new custom drop handler.
* \note Ownership of the factory is not transferred, and the factory must
* Register a new custom drop \a handler.
* \note Ownership of \a handler is not transferred, and the handler must
* be unregistered when plugin is unloaded.
* \see QgsCustomDropHandler
* \see unregisterCustomDropHandler()
Expand All @@ -967,16 +968,34 @@ class GUI_EXPORT QgisInterface : public QObject
virtual void registerCustomDropHandler( QgsCustomDropHandler *handler ) = 0;

/**
* Unregister a previously registered custom drop handler.
* Unregister a previously registered custom drop \a handler.
* \see QgsCustomDropHandler
* \see registerCustomDropHandler()
* \since QGIS 3.0
*/
virtual void unregisterCustomDropHandler( QgsCustomDropHandler *handler ) = 0;

/**
* Register a new custom project open \a handler.
* \note Ownership of \a handler is not transferred, and the handler must
* be unregistered when plugin is unloaded.
* \see QgsCustomProjectOpenHandler
* \see unregisterCustomProjectOpenHandler()
* \since QGIS 3.14
*/
virtual void registerCustomProjectOpenHandler( QgsCustomProjectOpenHandler *handler ) = 0;

/**
* Unregister a previously registered custom project open \a handler.
* \see QgsCustomDropHandler
* \see registerCustomProjectOpenHandler()
* \since QGIS 3.14
*/
virtual void unregisterCustomProjectOpenHandler( QgsCustomProjectOpenHandler *handler ) = 0;

/**
* Register a new custom drop \a handler for layout windows.
* \note Ownership of the factory is not transferred, and the factory must
* \note Ownership of \a handler is not transferred, and the handler must
* be unregistered when plugin is unloaded.
* \see QgsLayoutCustomDropHandler
* \see unregisterCustomLayoutDropHandler()
Expand Down
16 changes: 16 additions & 0 deletions src/gui/qgscustomprojectopenhandler.cpp
@@ -0,0 +1,16 @@
/***************************************************************************
qgscustomprojectopenhandler.h
---------------------
begin : April 2020
copyright : (C) 2020 by Nyall Dawson
email : nyall dot dawson at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/

#include "qgscustomprojectopenhandler.h"

0 comments on commit 2716415

Please sign in to comment.