Add API to QgsCustomDropHandler to support custom drop handlers which

allow drops onto a QgsMapCanvas
This commit is contained in:
Nyall Dawson 2019-09-05 10:52:02 +10:00
parent e391b43b55
commit 58975e7cee
9 changed files with 286 additions and 73 deletions

View File

@ -8,6 +8,7 @@
class QgsCustomDropHandler : QObject
{
%Docstring
@ -123,6 +124,49 @@ The base class implementation does nothing.
This method is not called directly while drop handling is occurring,
so the limitations described in handleMimeData() about returning
quickly do not apply.
%End
virtual bool canHandleCustomUriCanvasDrop( const QgsMimeDataUtils::Uri &uri, QgsMapCanvas *canvas );
%Docstring
Returns ``True`` if the handler is capable of handling the provided mime ``uri``
when dropped onto a map ``canvas``.
The base class implementation returns ``False`` regardless of mime data.
This method is called when mime data is dragged over a map canvas, in order
to determine whether any handlers are capable of handling the data and to
determine whether the drag action should be accepted.
.. warning::
Subclasses should be very careful about implementing this. If they
incorrectly return ``True`` to a ``uri``, it will prevent the default application
drop handling from occurring and will break the ability to drag and drop layers
and files onto QGIS.
.. versionadded:: 3.10
%End
virtual bool handleCustomUriCanvasDrop( const QgsMimeDataUtils::Uri &uri, QgsMapCanvas *canvas ) const;
%Docstring
Called from QGIS after a drop event with custom ``uri`` known by the handler occurs
onto a map ``canvas``.
In order for handleCustomUriCanvasDrop() to be called, subclasses must
also implement customUriProviderKey() to indicate the providerKey
value which the handler accepts.
If the function returns ``True``, it means the handler has accepted the drop
and it should not be further processed (e.g. by other QgsCustomDropHandlers).
Subclasses which implement this must also implement corresponding versions of
canHandleCustomUriCanvasDrop().
.. seealso:: :py:func:`customUriProviderKey`
.. seealso:: :py:func:`canHandleCustomUriCanvasDrop`
.. versionadded:: 3.10
%End
};

View File

@ -719,6 +719,7 @@ for the canvas.
.. versionadded:: 3.0
%End
public slots:
void refresh();
@ -983,75 +984,36 @@ The ``layer`` argument indicates the associated map layer, if available.
virtual bool event( QEvent *e );
%Docstring
Overridden standard event to be gestures aware
%End
virtual void keyPressEvent( QKeyEvent *e );
%Docstring
Overridden key press event
%End
virtual void keyReleaseEvent( QKeyEvent *e );
%Docstring
Overridden key release event
%End
virtual void mouseDoubleClickEvent( QMouseEvent *e );
%Docstring
Overridden mouse double-click event
%End
virtual void mouseMoveEvent( QMouseEvent *e );
%Docstring
Overridden mouse move event
%End
virtual void mousePressEvent( QMouseEvent *e );
%Docstring
Overridden mouse press event
%End
virtual void mouseReleaseEvent( QMouseEvent *e );
%Docstring
Overridden mouse release event
%End
virtual void wheelEvent( QWheelEvent *e );
%Docstring
Overridden mouse wheel event
%End
virtual void resizeEvent( QResizeEvent *e );
%Docstring
Overridden resize event
%End
virtual void paintEvent( QPaintEvent *e );
%Docstring
Overridden paint event
%End
virtual void dragEnterEvent( QDragEnterEvent *e );
%Docstring
Overridden drag enter event
%End
void moveCanvasContents( bool reset = false );
%Docstring
called when panning is in action, reset indicates end of panning
%End
virtual void dropEvent( QDropEvent *event );

View File

@ -1777,11 +1777,28 @@ void QgisApp::registerCustomDropHandler( QgsCustomDropHandler *handler )
{
if ( !mCustomDropHandlers.contains( handler ) )
mCustomDropHandlers << handler;
const auto canvases = mapCanvases();
for ( QgsMapCanvas *canvas : canvases )
{
canvas->setCustomDropHandlers( mCustomDropHandlers );
}
}
void QgisApp::unregisterCustomDropHandler( QgsCustomDropHandler *handler )
{
mCustomDropHandlers.removeOne( handler );
const auto canvases = mapCanvases();
for ( QgsMapCanvas *canvas : canvases )
{
canvas->setCustomDropHandlers( mCustomDropHandlers );
}
}
QVector<QPointer<QgsCustomDropHandler> > QgisApp::customDropHandlers() const
{
return mCustomDropHandlers;
}
void QgisApp::registerCustomLayoutDropHandler( QgsLayoutCustomDropHandler *handler )
@ -3877,6 +3894,8 @@ QgsMapCanvasDockWidget *QgisApp::createNewMapCanvasDock( const QString &name )
Q_UNUSED( canvasItem ) //item is already added automatically to canvas scene
}
mapCanvas->setCustomDropHandlers( mCustomDropHandlers );
markDirty();
connect( mapCanvasWidget, &QgsMapCanvasDockWidget::closed, this, &QgisApp::markDirty );
connect( mapCanvasWidget, &QgsMapCanvasDockWidget::renameTriggered, this, &QgisApp::renameView );

View File

@ -666,6 +666,9 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow
//! Unregister a previously registered custom drop handler.
void unregisterCustomDropHandler( QgsCustomDropHandler *handler );
//! Returns a list of registered custom drop handlers.
QVector<QPointer<QgsCustomDropHandler >> customDropHandlers() const;
//! Register a new custom layout drop handler.
void registerCustomLayoutDropHandler( QgsLayoutCustomDropHandler *handler );

View File

@ -45,3 +45,13 @@ bool QgsCustomDropHandler::handleFileDrop( const QString &file )
Q_UNUSED( file )
return false;
}
bool QgsCustomDropHandler::canHandleCustomUriCanvasDrop( const QgsMimeDataUtils::Uri &, QgsMapCanvas * )
{
return false;
}
bool QgsCustomDropHandler::handleCustomUriCanvasDrop( const QgsMimeDataUtils::Uri &, QgsMapCanvas * ) const
{
return false;
}

View File

@ -19,6 +19,8 @@
#include "qgsmimedatautils.h"
#include "qgis_gui.h"
class QgsMapCanvas;
/**
* \ingroup gui
* Abstract base class that may be implemented to handle new types of data to be dropped in QGIS.
@ -135,6 +137,45 @@ class GUI_EXPORT QgsCustomDropHandler : public QObject
* quickly do not apply.
*/
virtual bool handleFileDrop( const QString &file );
/**
* Returns TRUE if the handler is capable of handling the provided mime \a uri
* when dropped onto a map \a canvas.
*
* The base class implementation returns FALSE regardless of mime data.
*
* This method is called when mime data is dragged over a map canvas, in order
* to determine whether any handlers are capable of handling the data and to
* determine whether the drag action should be accepted.
*
* \warning Subclasses should be very careful about implementing this. If they
* incorrectly return TRUE to a \a uri, it will prevent the default application
* drop handling from occurring and will break the ability to drag and drop layers
* and files onto QGIS.
*
* \since QGIS 3.10
*/
virtual bool canHandleCustomUriCanvasDrop( const QgsMimeDataUtils::Uri &uri, QgsMapCanvas *canvas );
/**
* Called from QGIS after a drop event with custom \a uri known by the handler occurs
* onto a map \a canvas.
*
* In order for handleCustomUriCanvasDrop() to be called, subclasses must
* also implement customUriProviderKey() to indicate the providerKey
* value which the handler accepts.
*
* If the function returns TRUE, it means the handler has accepted the drop
* and it should not be further processed (e.g. by other QgsCustomDropHandlers).
*
* Subclasses which implement this must also implement corresponding versions of
* canHandleCustomUriCanvasDrop().
*
* \see customUriProviderKey()
* \see canHandleCustomUriCanvasDrop()
* \since QGIS 3.10
*/
virtual bool handleCustomUriCanvasDrop( const QgsMimeDataUtils::Uri &uri, QgsMapCanvas *canvas ) const;
};
#endif // QGSCUSTOMDROPHANDLER_H

View File

@ -71,6 +71,8 @@ email : sherman at mrcc.com
#include "qgssvgcache.h"
#include "qgsimagecache.h"
#include "qgsexpressioncontextutils.h"
#include "qgsmimedatautils.h"
#include "qgscustomdrophandler.h"
/**
* \ingroup gui
@ -716,6 +718,11 @@ void QgsMapCanvas::setPreviewJobsEnabled( bool enabled )
mUsePreviewJobs = enabled;
}
void QgsMapCanvas::setCustomDropHandlers( const QVector<QPointer<QgsCustomDropHandler> > &handlers )
{
mDropHandlers = handlers;
}
void QgsMapCanvas::mapUpdateTimeout()
{
if ( mJob )
@ -1988,6 +1995,40 @@ void QgsMapCanvas::moveCanvasContents( bool reset )
setSceneRect( -pnt.x(), -pnt.y(), viewport()->size().width(), viewport()->size().height() );
}
void QgsMapCanvas::dropEvent( QDropEvent *event )
{
if ( QgsMimeDataUtils::isUriList( event->mimeData() ) )
{
const QgsMimeDataUtils::UriList lst = QgsMimeDataUtils::decodeUriList( event->mimeData() );
bool allHandled = true;
for ( const QgsMimeDataUtils::Uri &uri : lst )
{
bool handled = false;
for ( QgsCustomDropHandler *handler : qgis::as_const( mDropHandlers ) )
{
if ( handler && handler->customUriProviderKey() == uri.providerKey )
{
if ( handler->handleCustomUriCanvasDrop( uri, this ) )
{
handled = true;
break;
}
}
}
if ( !handled )
allHandled = false;
}
if ( allHandled )
event->accept();
else
event->ignore();
}
else
{
event->ignore();
}
}
QPoint QgsMapCanvas::mouseLastXY()
{
return mCanvasProperties->mouseLastXY;
@ -2170,12 +2211,42 @@ void QgsMapCanvas::selectionChangedSlot()
}
}
void QgsMapCanvas::dragEnterEvent( QDragEnterEvent *e )
void QgsMapCanvas::dragEnterEvent( QDragEnterEvent *event )
{
// By default graphics view delegates the drag events to graphics items.
// But we do not want that and by ignoring the drag enter we let the
// parent (e.g. QgisApp) to handle drops of map layers etc.
e->ignore();
// so we ONLY accept the event if we know in advance that a custom drop handler
// wants it
if ( QgsMimeDataUtils::isUriList( event->mimeData() ) )
{
const QgsMimeDataUtils::UriList lst = QgsMimeDataUtils::decodeUriList( event->mimeData() );
bool allHandled = true;
for ( const QgsMimeDataUtils::Uri &uri : lst )
{
bool handled = false;
for ( QgsCustomDropHandler *handler : qgis::as_const( mDropHandlers ) )
{
if ( handler->canHandleCustomUriCanvasDrop( uri, this ) )
{
handled = true;
break;
}
}
if ( !handled )
allHandled = false;
}
if ( allHandled )
event->accept();
else
event->ignore();
}
else
{
event->ignore();
}
}
void QgsMapCanvas::mapToolDestroyed()

View File

@ -25,6 +25,7 @@
#include "qgsrectangle.h"
#include "qgsfeatureid.h"
#include "qgsgeometry.h"
#include "qgscustomdrophandler.h"
#include <QDomDocument>
#include <QGraphicsView>
@ -639,6 +640,13 @@ class GUI_EXPORT QgsMapCanvas : public QGraphicsView
*/
void setPreviewJobsEnabled( bool enabled );
/**
* Sets a list of custom drop \a handlers to use when drop events occur on the canvas.
* \note Not available in Python bindings
* \since QGIS 3.10
*/
void setCustomDropHandlers( const QVector<QPointer<QgsCustomDropHandler >> &handlers ) SIP_SKIP;
public slots:
//! Repaints the canvas map
@ -863,42 +871,24 @@ class GUI_EXPORT QgsMapCanvas : public QGraphicsView
protected:
//! Overridden standard event to be gestures aware
bool event( QEvent *e ) override;
//! Overridden key press event
void keyPressEvent( QKeyEvent *e ) override;
//! Overridden key release event
void keyReleaseEvent( QKeyEvent *e ) override;
//! Overridden mouse double-click event
void mouseDoubleClickEvent( QMouseEvent *e ) override;
//! Overridden mouse move event
void mouseMoveEvent( QMouseEvent *e ) override;
//! Overridden mouse press event
void mousePressEvent( QMouseEvent *e ) override;
//! Overridden mouse release event
void mouseReleaseEvent( QMouseEvent *e ) override;
//! Overridden mouse wheel event
void wheelEvent( QWheelEvent *e ) override;
//! Overridden resize event
void resizeEvent( QResizeEvent *e ) override;
//! Overridden paint event
void paintEvent( QPaintEvent *e ) override;
//! Overridden drag enter event
void dragEnterEvent( QDragEnterEvent *e ) override;
//! called when panning is in action, reset indicates end of panning
void moveCanvasContents( bool reset = false );
void dropEvent( QDropEvent *event ) override;
/// implementation struct
class CanvasProperties;
@ -1029,6 +1019,8 @@ class GUI_EXPORT QgsMapCanvas : public QGraphicsView
QHash< QString, int > mLastLayerRenderTime;
QVector<QPointer<QgsCustomDropHandler >> mDropHandlers;
/**
* Returns the last cursor position on the canvas in geographical coordinates
* \since QGIS 3.4

View File

@ -15,13 +15,14 @@
#include "qgstest.h"
#include <qgsapplication.h>
#include <qgsmapcanvas.h>
#include <qgsvectorlayer.h>
#include <qgsproject.h>
#include <qgsrenderchecker.h>
#include <qgsvectordataprovider.h>
#include <qgsmaptoolpan.h>
#include "qgsapplication.h"
#include "qgsmapcanvas.h"
#include "qgsvectorlayer.h"
#include "qgsproject.h"
#include "qgsrenderchecker.h"
#include "qgsvectordataprovider.h"
#include "qgsmaptoolpan.h"
#include "qgscustomdrophandler.h"
namespace QTest
{
@ -57,6 +58,7 @@ class TestQgsMapCanvas : public QObject
void testScaleLockCanvasResize();
void testZoomByWheel();
void testShiftZoom();
void testDragDrop();
private:
QgsMapCanvas *mCanvas = nullptr;
@ -431,5 +433,74 @@ void TestQgsMapCanvas::testShiftZoom()
QGSCOMPARENEAR( mCanvas->extent().height(), originalHeight, 0.00001 );
}
class TestNoDropHandler : public QgsCustomDropHandler
{
Q_OBJECT
public:
QString customUriProviderKey() const override { return QStringLiteral( "test" ); }
bool canHandleCustomUriCanvasDrop( const QgsMimeDataUtils::Uri &, QgsMapCanvas * ) override { return false; }
bool handleCustomUriCanvasDrop( const QgsMimeDataUtils::Uri &, QgsMapCanvas * ) const override { return false; }
};
class TestYesDropHandler : public QgsCustomDropHandler
{
Q_OBJECT
public:
QString customUriProviderKey() const override { return QStringLiteral( "test" ); }
bool canHandleCustomUriCanvasDrop( const QgsMimeDataUtils::Uri &, QgsMapCanvas * ) override { return true; }
bool handleCustomUriCanvasDrop( const QgsMimeDataUtils::Uri &, QgsMapCanvas * ) const override { return true; }
};
void TestQgsMapCanvas::testDragDrop()
{
// default drag, should not be accepted
std::unique_ptr< QMimeData > data = qgis::make_unique< QMimeData >();
std::unique_ptr< QDragEnterEvent > event = qgis::make_unique< QDragEnterEvent >( QPoint( 10, 10 ), Qt::CopyAction, data.get(), Qt::LeftButton, Qt::NoModifier );
mCanvas->dragEnterEvent( event.get() );
QVERIFY( !event->isAccepted() );
// with mime data
QgsMimeDataUtils::UriList list;
QgsMimeDataUtils::Uri uri;
uri.name = QStringLiteral( "name" );
uri.providerKey = QStringLiteral( "test" );
list << uri;
data.reset( QgsMimeDataUtils::encodeUriList( list ) );
event = qgis::make_unique< QDragEnterEvent >( QPoint( 10, 10 ), Qt::CopyAction, data.get(), Qt::LeftButton, Qt::NoModifier );
mCanvas->dragEnterEvent( event.get() );
// still not accepted by default
QVERIFY( !event->isAccepted() );
// add a custom drop handler to the canvas
TestNoDropHandler handler;
mCanvas->setCustomDropHandlers( QVector< QPointer< QgsCustomDropHandler > >() << &handler );
mCanvas->dragEnterEvent( event.get() );
// not accepted by handler
QVERIFY( !event->isAccepted() );
TestYesDropHandler handler2;
mCanvas->setCustomDropHandlers( QVector< QPointer< QgsCustomDropHandler > >() << &handler << &handler2 );
mCanvas->dragEnterEvent( event.get() );
// IS accepted by handler
QVERIFY( event->isAccepted() );
// check drop event logic
mCanvas->setCustomDropHandlers( QVector< QPointer< QgsCustomDropHandler > >() );
std::unique_ptr< QDropEvent > dropEvent = qgis::make_unique< QDropEvent >( QPoint( 10, 10 ), Qt::CopyAction, data.get(), Qt::LeftButton, Qt::NoModifier );
mCanvas->dropEvent( dropEvent.get() );
QVERIFY( !dropEvent->isAccepted() );
mCanvas->setCustomDropHandlers( QVector< QPointer< QgsCustomDropHandler > >() << &handler );
mCanvas->dropEvent( dropEvent.get() );
QVERIFY( !dropEvent->isAccepted() );
mCanvas->setCustomDropHandlers( QVector< QPointer< QgsCustomDropHandler > >() << &handler << &handler2 );
mCanvas->dropEvent( dropEvent.get() );
// is accepted!
QVERIFY( dropEvent->isAccepted() );
}
QGSTEST_MAIN( TestQgsMapCanvas )
#include "testqgsmapcanvas.moc"