Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Allow plugins and scripts to register custom logic to prevent
the QGIS application from exiting

This interface allows plugins to implement custom logic to determine whether it is safe
for the application to exit, e.g. by checking whether the plugin or script has any
unsaved changes which should be saved or discarded before allowing QGIS to exit.
  • Loading branch information
nyalldawson committed Oct 6, 2020
1 parent 64bf347 commit df6c8d7
Show file tree
Hide file tree
Showing 10 changed files with 261 additions and 1 deletion.
24 changes: 24 additions & 0 deletions python/gui/auto_generated/qgisinterface.sip.in
Expand Up @@ -1234,6 +1234,30 @@ Unregister a previously registered tool factory from the development/debugging t
.. seealso:: :py:func:`registerDevToolWidgetFactory`

.. versionadded:: 3.14
%End

virtual void registerApplicationExitBlocker( QgsApplicationExitBlockerInterface *blocker ) = 0;
%Docstring
Register a new application exit blocker, which can be used to prevent the QGIS application
from exiting while a plugin or script has unsaved changes.

.. note::

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

.. seealso:: :py:func:`unregisterApplicationExitBlocker`

.. versionadded:: 3.16
%End

virtual void unregisterApplicationExitBlocker( QgsApplicationExitBlockerInterface *blocker ) = 0;
%Docstring
Unregister a previously registered application exit ``blocker``.

.. seealso:: :py:func:`registerApplicationExitBlocker`

.. versionadded:: 3.16
%End

virtual void registerCustomDropHandler( QgsCustomDropHandler *handler ) = 0;
Expand Down
@@ -0,0 +1,77 @@
/************************************************************************
* This file has been generated automatically from *
* *
* src/gui/qgsapplicationexitblockerinterface.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/



class QgsApplicationExitBlockerInterface
{
%Docstring
An interface that may be implemented to allow plugins or scripts to temporarily block
the QGIS application from exiting.

This interface allows plugins to implement custom logic to determine whether it is safe
for the application to exit, e.g. by checking whether the plugin or script has any
unsaved changes which should be saved or discarded before allowing QGIS to exit.

QgsApplicationExitBlockerInterface are registered via the iface object:

Example
-------

.. code-block:: python

class MyPluginExitBlocker(QgsApplicationExitBlockerInterface):

def allowExit(self):
if self.has_unsaved_changes():
# show a warning prompt
# ...
# prevent QGIS application from exiting
return False

# allow QGIS application to exit
return True

my_blocker = MyPluginExitBlocker()
iface.registerApplicationExitBlocker(my_blocker)

.. versionadded:: 3.16
%End

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

virtual ~QgsApplicationExitBlockerInterface();

virtual bool allowExit() = 0;
%Docstring
Called whenever the QGIS application has been asked to exit by a user.

The subclass can use this method to implement custom logic handling whether it is safe
for the application to exit, e.g. by checking whether the plugin or script has any unsaved
changes which should be saved or discarded before allowing QGIS to exit.

The implementation should return ``True`` if it is safe for QGIS to exit, or ``False`` if it
wishes to prevent the application from exiting.

.. note::

It is safe to use GUI widgets in implementations of this function, including message
boxes or custom dialogs with event loops.
%End
};

/************************************************************************
* This file has been generated automatically from *
* *
* src/gui/qgsapplicationexitblockerinterface.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 @@ -7,6 +7,7 @@
%Include auto_generated/qgsadvanceddigitizingfloater.sip
%Include auto_generated/qgsaggregatetoolbutton.sip
%Include auto_generated/qgsalignmentcombobox.sip
%Include auto_generated/qgsapplicationexitblockerinterface.sip
%Include auto_generated/qgsattributedialog.sip
%Include auto_generated/qgsattributeeditorcontext.sip
%Include auto_generated/qgsattributeform.sip
Expand Down
23 changes: 22 additions & 1 deletion src/app/qgisapp.cpp
Expand Up @@ -181,6 +181,7 @@ Q_GUI_EXPORT extern int qt_defaultDpiX();
#include "qgsauthsslerrorsdialog.h"
#endif
#include "qgsappscreenshots.h"
#include "qgsapplicationexitblockerinterface.h"
#include "qgsbookmarks.h"
#include "qgsbookmarkeditordialog.h"
#include "qgsbrowserdockwidget.h"
Expand Down Expand Up @@ -6388,7 +6389,7 @@ void QgisApp::fileExit()
}

QgsCanvasRefreshBlocker refreshBlocker;
if ( checkUnsavedLayerEdits() && checkMemoryLayers() && saveDirty() )
if ( checkUnsavedLayerEdits() && checkMemoryLayers() && checkExitBlockers() && saveDirty() )
{
closeProject();
userProfileManager()->setDefaultFromActive();
Expand Down Expand Up @@ -12686,6 +12687,16 @@ void QgisApp::unregisterDevToolFactory( QgsDevToolWidgetFactory *factory )
mDevToolFactories.removeAll( factory );
}

void QgisApp::registerApplicationExitBlocker( QgsApplicationExitBlockerInterface *blocker )
{
mApplicationExitBlockers << blocker;
}

void QgisApp::unregisterApplicationExitBlocker( QgsApplicationExitBlockerInterface *blocker )
{
mApplicationExitBlockers.removeAll( blocker );
}

QgsMapLayer *QgisApp::activeLayer()
{
return mLayerTreeView ? mLayerTreeView->currentLayer() : nullptr;
Expand Down Expand Up @@ -13237,6 +13248,16 @@ bool QgisApp::checkMemoryLayers()
return close;
}

bool QgisApp::checkExitBlockers()
{
for ( QgsApplicationExitBlockerInterface *blocker : qgis::as_const( mApplicationExitBlockers ) )
{
if ( !blocker->allowExit() )
return false;
}
return true;
}

bool QgisApp::checkTasksDependOnProject()
{
QSet< QString > activeTaskDescriptions;
Expand Down
26 changes: 26 additions & 0 deletions src/app/qgisapp.h
Expand Up @@ -111,6 +111,7 @@ class Qgs3DMapCanvasDockWidget;
class QgsHandleBadLayersHandler;
class QgsNetworkAccessManager;
class QgsGpsConnection;
class QgsApplicationExitBlockerInterface;

class QDomDocument;
class QNetworkReply;
Expand Down Expand Up @@ -746,6 +747,23 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow
//! Unregister a previously registered dev tool factory
void unregisterDevToolFactory( QgsDevToolWidgetFactory *factory );

/**
* Register a new application exit blocker, which can be used to prevent the QGIS application
* from exiting while a plugin or script has unsaved changes.
*
* \note Ownership of \a blocker is not transferred, and the blocker must
* be unregistered when plugin is unloaded.
*
* \see unregisterApplicationExitBlocker()
*/
void registerApplicationExitBlocker( QgsApplicationExitBlockerInterface *blocker );

/**
* Unregister a previously registered application exit \a blocker.
* \see registerApplicationExitBlocker()
*/
void unregisterApplicationExitBlocker( QgsApplicationExitBlockerInterface *blocker );

//! Register a new custom drop handler.
void registerCustomDropHandler( QgsCustomDropHandler *handler );

Expand Down Expand Up @@ -2094,6 +2112,12 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow
*/
bool checkMemoryLayers();

/**
* Checks whether any registered application exit blockers should prevent
* the application from exiting.
*/
bool checkExitBlockers();

//! Checks for running tasks dependent on the open project
bool checkTasksDependOnProject();

Expand Down Expand Up @@ -2569,6 +2593,8 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow

QList<QgsDevToolWidgetFactory * > mDevToolFactories;

QList<QgsApplicationExitBlockerInterface * > mApplicationExitBlockers;

QVector<QPointer<QgsCustomDropHandler>> mCustomDropHandlers;
QVector<QPointer<QgsCustomProjectOpenHandler>> mCustomProjectOpenHandlers;
QVector<QPointer<QgsLayoutCustomDropHandler>> mCustomLayoutDropHandlers;
Expand Down
10 changes: 10 additions & 0 deletions src/app/qgisappinterface.cpp
Expand Up @@ -571,6 +571,16 @@ void QgisAppInterface::unregisterDevToolWidgetFactory( QgsDevToolWidgetFactory *
qgis->unregisterDevToolFactory( factory );
}

void QgisAppInterface::registerApplicationExitBlocker( QgsApplicationExitBlockerInterface *blocker )
{
qgis->registerApplicationExitBlocker( blocker );
}

void QgisAppInterface::unregisterApplicationExitBlocker( QgsApplicationExitBlockerInterface *blocker )
{
qgis->unregisterApplicationExitBlocker( blocker );
}

void QgisAppInterface::registerCustomDropHandler( QgsCustomDropHandler *handler )
{
qgis->registerCustomDropHandler( handler );
Expand Down
2 changes: 2 additions & 0 deletions src/app/qgisappinterface.h
Expand Up @@ -150,6 +150,8 @@ class APP_EXPORT QgisAppInterface : public QgisInterface
void unregisterProjectPropertiesWidgetFactory( QgsOptionsWidgetFactory *factory ) override;
void registerDevToolWidgetFactory( QgsDevToolWidgetFactory *factory ) override;
void unregisterDevToolWidgetFactory( QgsDevToolWidgetFactory *factory ) override;
void registerApplicationExitBlocker( QgsApplicationExitBlockerInterface *blocker ) override;
void unregisterApplicationExitBlocker( QgsApplicationExitBlockerInterface *blocker ) override;
void registerCustomDropHandler( QgsCustomDropHandler *handler ) override;
void unregisterCustomDropHandler( QgsCustomDropHandler *handler ) override;
void registerCustomProjectOpenHandler( QgsCustomProjectOpenHandler *handler ) override;
Expand Down
1 change: 1 addition & 0 deletions src/gui/CMakeLists.txt
Expand Up @@ -592,6 +592,7 @@ SET(QGIS_GUI_HDRS
qgsadvanceddigitizingfloater.h
qgsaggregatetoolbutton.h
qgsalignmentcombobox.h
qgsapplicationexitblockerinterface.h
qgsattributedialog.h
qgsattributeeditorcontext.h
qgsattributeform.h
Expand Down
20 changes: 20 additions & 0 deletions src/gui/qgisinterface.h
Expand Up @@ -65,6 +65,7 @@ class QgsMeshLayer;
class QgsBrowserGuiModel;
class QgsDevToolWidgetFactory;
class QgsGpsConnection;
class QgsApplicationExitBlockerInterface;


/**
Expand Down Expand Up @@ -1025,6 +1026,25 @@ class GUI_EXPORT QgisInterface : public QObject
*/
virtual void unregisterDevToolWidgetFactory( QgsDevToolWidgetFactory *factory ) = 0;

/**
* Register a new application exit blocker, which can be used to prevent the QGIS application
* from exiting while a plugin or script has unsaved changes.
*
* \note Ownership of \a blocker is not transferred, and the blocker must
* be unregistered when plugin is unloaded.
*
* \see unregisterApplicationExitBlocker()
* \since QGIS 3.16
*/
virtual void registerApplicationExitBlocker( QgsApplicationExitBlockerInterface *blocker ) = 0;

/**
* Unregister a previously registered application exit \a blocker.
* \see registerApplicationExitBlocker()
* \since QGIS 3.16
*/
virtual void unregisterApplicationExitBlocker( QgsApplicationExitBlockerInterface *blocker ) = 0;

/**
* Register a new custom drop \a handler.
* \note Ownership of \a handler is not transferred, and the handler must
Expand Down
78 changes: 78 additions & 0 deletions src/gui/qgsapplicationexitblockerinterface.h
@@ -0,0 +1,78 @@
/***************************************************************************
qgsapplicationexitblockerinterface.h
---------------------
begin : October 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. *
* *
***************************************************************************/

#ifndef QGSAPPLICATIONEXITBLOCKERINTERFACE_H
#define QGSAPPLICATIONEXITBLOCKERINTERFACE_H

#include "qgis_gui.h"
#include <QStringList>
#include <QObject>

/**
* \ingroup gui
* An interface that may be implemented to allow plugins or scripts to temporarily block
* the QGIS application from exiting.
*
* This interface allows plugins to implement custom logic to determine whether it is safe
* for the application to exit, e.g. by checking whether the plugin or script has any
* unsaved changes which should be saved or discarded before allowing QGIS to exit.
*
* QgsApplicationExitBlockerInterface are registered via the iface object:
*
* ### Example
*
* \code{.py}
* class MyPluginExitBlocker(QgsApplicationExitBlockerInterface):
*
* def allowExit(self):
* if self.has_unsaved_changes():
* # show a warning prompt
* # ...
* # prevent QGIS application from exiting
* return False
*
* # allow QGIS application to exit
* return True
*
* my_blocker = MyPluginExitBlocker()
* iface.registerApplicationExitBlocker(my_blocker)
* \endcode
*
* \since QGIS 3.16
*/
class GUI_EXPORT QgsApplicationExitBlockerInterface
{

public:

virtual ~QgsApplicationExitBlockerInterface() = default;

/**
* Called whenever the QGIS application has been asked to exit by a user.
*
* The subclass can use this method to implement custom logic handling whether it is safe
* for the application to exit, e.g. by checking whether the plugin or script has any unsaved
* changes which should be saved or discarded before allowing QGIS to exit.
*
* The implementation should return TRUE if it is safe for QGIS to exit, or FALSE if it
* wishes to prevent the application from exiting.
*
* \note It is safe to use GUI widgets in implementations of this function, including message
* boxes or custom dialogs with event loops.
*/
virtual bool allowExit() = 0;
};

#endif // QgsCustomProjectOpenHandler_H

0 comments on commit df6c8d7

Please sign in to comment.