Skip to content

Commit b4fe900

Browse files
authored
[FEATURE] API to allow drag'n'drop of custom browser items
QgsDataItem implementations may provide hasDragEnabled(), mimeUri() and QgsCustomDropHandler implementation to deal with drop of custom items.
1 parent 2ea828e commit b4fe900

20 files changed

+315
-75
lines changed

doc/api_break.dox

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -450,6 +450,12 @@ be returned instead of a null pointer if no transformation is required.</li>
450450
plugins calling this method will need to be updated.</li>
451451
</ul>
452452

453+
\subsection qgis_api_break_3_0_QgsMimeDataUtils QgsMimeDataUtils
454+
455+
<ul>
456+
<li>Constructor taking QgsLayerItem argument has been removed. Use QgsDataItem::mimeUri() instead.</li>
457+
</ul>
458+
453459
\subsection qgis_api_break_3_0_QgsOSMElement QgsOSMElement
454460

455461
<ul>

python/core/qgsdataitem.sip

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,21 @@ class QgsDataItem : QObject
135135
*/
136136
virtual bool handleDrop( const QMimeData * /*data*/, Qt::DropAction /*action*/ );
137137

138+
/** Returns true if the item may be dragged.
139+
* Default implementation returns false.
140+
* A draggable item has to implement mimeUri() that will be used to pass data.
141+
* @see mimeUri()
142+
* @note added in 3.0
143+
*/
144+
virtual bool hasDragEnabled() const;
145+
146+
/** Return mime URI for the data item.
147+
* Items that return valid URI will be returned in mime data when dragging a selection from browser model.
148+
* @see hasDragEnabled()
149+
* @note added in 3.0
150+
*/
151+
virtual QgsMimeDataUtils::Uri mimeUri() const;
152+
138153
enum Capability
139154
{
140155
NoCapabilities,
@@ -262,26 +277,30 @@ class QgsLayerItem : QgsDataItem
262277

263278
virtual bool equal( const QgsDataItem *other );
264279

280+
virtual bool hasDragEnabled() const;
281+
282+
virtual QgsMimeDataUtils::Uri mimeUri() const;
283+
265284
// --- New virtual methods for layer item derived classes ---
266285

267286
// Returns QgsMapLayer::LayerType
268-
QgsMapLayer::LayerType mapLayerType();
287+
QgsMapLayer::LayerType mapLayerType() const;
269288

270289
// Returns layer uri or empty string if layer cannot be created
271-
QString uri();
290+
QString uri() const;
272291

273292
// Returns provider key
274-
QString providerKey();
293+
QString providerKey() const;
275294

276295
/** Returns the supported CRS
277296
* @note Added in 2.8
278297
*/
279-
QStringList supportedCrs();
298+
QStringList supportedCrs() const;
280299

281300
/** Returns the supported formats
282301
* @note Added in 2.8
283302
*/
284-
QStringList supportedFormats();
303+
QStringList supportedFormats() const;
285304

286305
/** Returns comments of the layer
287306
* @note added in 2.12
@@ -389,6 +408,7 @@ class QgsProjectItem : QgsDataItem
389408
QgsProjectItem( QgsDataItem* parent, const QString& name, const QString& path );
390409
~QgsProjectItem();
391410

411+
virtual bool hasDragEnabled() const;
392412
};
393413

394414
/**

python/core/qgsmimedatautils.sip

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,27 @@ class QgsMimeDataUtils
77

88
struct Uri
99
{
10-
Uri( QgsLayerItem* layer );
11-
Uri( QString& encData );
10+
//! Constructs invalid URI
11+
Uri();
12+
//! Constructs URI from encoded data
13+
explicit Uri( QString& encData );
1214

15+
//! Returns whether the object contains valid data
16+
//! @note added in 3.0
17+
bool isValid() const;
18+
19+
//! Returns encoded representation of the object
1320
QString data() const;
1421

22+
//! Type of URI. Recognized types: "vector" / "raster" / "plugin" / "custom"
1523
QString layerType;
24+
//! For "vector" / "raster" type: provider id.
25+
//! For "plugin" type: plugin layer type name.
26+
//! For "custom" type: key of its QgsCustomDropHandler
1627
QString providerKey;
28+
//! Human readable name to be used e.g. in layer tree
1729
QString name;
30+
//! Identifier of the data source recognized by its providerKey
1831
QString uri;
1932
QStringList supportedCrs;
2033
QStringList supportedFormats;

python/gui/gui.sip

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
%Include qgscompoundcolorwidget.sip
5858
%Include qgsconfigureshortcutsdialog.sip
5959
%Include qgscredentialdialog.sip
60+
%Include qgscustomdrophandler.sip
6061
%Include qgsdatadefinedbutton.sip
6162
%Include qgsdetaileditemdata.sip
6263
%Include qgsdetaileditemdelegate.sip

python/gui/qgisinterface.sip

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,18 @@ class QgisInterface : QObject
292292
*/
293293
virtual void unregisterMapLayerConfigWidgetFactory( QgsMapLayerConfigWidgetFactory* factory ) = 0;
294294

295+
/** Register a new custom drop handler.
296+
* @note added in QGIS 3.0
297+
* @note Ownership of the factory is not transferred, and the factory must
298+
* be unregistered when plugin is unloaded.
299+
* @see unregisterCustomDropHandler() */
300+
virtual void registerCustomDropHandler( QgsCustomDropHandler* handler ) = 0;
301+
302+
/** Unregister a previously registered custom drop handler.
303+
* @note added in QGIS 3.0
304+
* @see registerCustomDropHandler() */
305+
virtual void unregisterCustomDropHandler( QgsCustomDropHandler* handler ) = 0;
306+
295307
// @todo is this deprecated in favour of QgsContextHelp?
296308
/** Open a url in the users browser. By default the QGIS doc directory is used
297309
* as the base for the URL. To open a URL that is not relative to the installed

python/gui/qgscustomdrophandler.sip

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/** \ingroup gui
2+
* Abstract base class that may be implemented to handle new types of data to be dropped in QGIS.
3+
* Implementations will be used when a QgsMimeDataUtils::Uri has layerType equal to "custom",
4+
* and the providerKey is equal to key() returned by the implementation.
5+
* @note added in QGIS 3.0
6+
*/
7+
class QgsCustomDropHandler
8+
{
9+
%TypeHeaderCode
10+
#include <qgscustomdrophandler.h>
11+
%End
12+
13+
public:
14+
virtual ~QgsCustomDropHandler();
15+
16+
//! Type of custom URI recognized by the handler
17+
virtual QString key() const = 0;
18+
19+
//! Method called from QGIS after a drop event with custom URI known by the handler
20+
virtual void handleDrop( const QgsMimeDataUtils::Uri& uri ) const = 0;
21+
};

src/app/qgisapp.cpp

Lines changed: 46 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@
128128
#include "qgscoordinateutils.h"
129129
#include "qgscredentialdialog.h"
130130
#include "qgscursors.h"
131+
#include "qgscustomdrophandler.h"
131132
#include "qgscustomization.h"
132133
#include "qgscustomlayerorderwidget.h"
133134
#include "qgscustomprojectiondialog.h"
@@ -1341,29 +1342,58 @@ void QgisApp::dropEvent( QDropEvent *event )
13411342
if ( QgsMimeDataUtils::isUriList( event->mimeData() ) )
13421343
{
13431344
QgsMimeDataUtils::UriList lst = QgsMimeDataUtils::decodeUriList( event->mimeData() );
1344-
Q_FOREACH ( const QgsMimeDataUtils::Uri& u, lst )
1345-
{
1346-
QString uri = crsAndFormatAdjustedLayerUri( u.uri, u.supportedCrs, u.supportedFormats );
1345+
handleDropUriList( lst );
1346+
}
1347+
mMapCanvas->freeze( false );
1348+
mMapCanvas->refresh();
1349+
event->acceptProposedAction();
1350+
}
13471351

1348-
if ( u.layerType == "vector" )
1349-
{
1350-
addVectorLayer( uri, u.name, u.providerKey );
1351-
}
1352-
else if ( u.layerType == "raster" )
1353-
{
1354-
addRasterLayer( uri, u.name, u.providerKey );
1355-
}
1356-
else if ( u.layerType == "plugin" )
1352+
1353+
void QgisApp::registerCustomDropHandler( QgsCustomDropHandler* handler )
1354+
{
1355+
if ( !mCustomDropHandlers.contains( handler ) )
1356+
mCustomDropHandlers << handler;
1357+
}
1358+
1359+
void QgisApp::unregisterCustomDropHandler( QgsCustomDropHandler* handler )
1360+
{
1361+
mCustomDropHandlers.removeOne( handler );
1362+
}
1363+
1364+
void QgisApp::handleDropUriList( const QgsMimeDataUtils::UriList& lst )
1365+
{
1366+
Q_FOREACH ( const QgsMimeDataUtils::Uri& u, lst )
1367+
{
1368+
QString uri = crsAndFormatAdjustedLayerUri( u.uri, u.supportedCrs, u.supportedFormats );
1369+
1370+
if ( u.layerType == "vector" )
1371+
{
1372+
addVectorLayer( uri, u.name, u.providerKey );
1373+
}
1374+
else if ( u.layerType == "raster" )
1375+
{
1376+
addRasterLayer( uri, u.name, u.providerKey );
1377+
}
1378+
else if ( u.layerType == "plugin" )
1379+
{
1380+
addPluginLayer( uri, u.name, u.providerKey );
1381+
}
1382+
else if ( u.layerType == "custom" )
1383+
{
1384+
Q_FOREACH ( QgsCustomDropHandler* handler, mCustomDropHandlers )
13571385
{
1358-
addPluginLayer( uri, u.name, u.providerKey );
1386+
if ( handler->key() == u.providerKey )
1387+
{
1388+
handler->handleDrop( u );
1389+
break;
1390+
}
13591391
}
13601392
}
13611393
}
1362-
mMapCanvas->freeze( false );
1363-
mMapCanvas->refresh();
1364-
event->acceptProposedAction();
13651394
}
13661395

1396+
13671397
bool QgisApp::event( QEvent * event )
13681398
{
13691399
bool done = false;

src/app/qgisapp.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ class QgsClipboard;
4444
class QgsComposer;
4545
class QgsComposerManager;
4646
class QgsComposerView;
47+
class QgsCustomDropHandler;
4748
class QgsStatusBarCoordinatesWidget;
4849
class QgsStatusBarMagnifierWidget;
4950
class QgsStatusBarScaleWidget;
@@ -120,6 +121,7 @@ class QgsDiagramProperties;
120121
#include "qgsfeature.h"
121122
#include "qgspoint.h"
122123
#include "qgsmessagebar.h"
124+
#include "qgsmimedatautils.h"
123125
#include "qgswelcomepageitemsmodel.h"
124126
#include "qgsraster.h"
125127

@@ -513,6 +515,15 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow
513515
/** Unregister a previously registered tab in the layer properties dialog */
514516
void unregisterMapLayerPropertiesFactory( QgsMapLayerConfigWidgetFactory* factory );
515517

518+
/** Register a new custom drop handler. */
519+
void registerCustomDropHandler( QgsCustomDropHandler* handler );
520+
521+
/** Unregister a previously registered custom drop handler. */
522+
void unregisterCustomDropHandler( QgsCustomDropHandler* handler );
523+
524+
/** Process the list of URIs that have been dropped in QGIS */
525+
void handleDropUriList( const QgsMimeDataUtils::UriList& lst );
526+
516527
public slots:
517528
void layerTreeViewDoubleClicked( const QModelIndex& index );
518529
//! Make sure the insertion point for new layers is up-to-date with the current item in layer tree view
@@ -1772,6 +1783,8 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow
17721783

17731784
QList<QgsMapLayerConfigWidgetFactory*> mMapLayerPanelFactories;
17741785

1786+
QList<QgsCustomDropHandler*> mCustomDropHandlers;
1787+
17751788
QDateTime mProjectLastModified;
17761789

17771790
QgsWelcomePage* mWelcomePage;

src/app/qgisappinterface.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -485,6 +485,16 @@ void QgisAppInterface::unregisterMapLayerConfigWidgetFactory( QgsMapLayerConfigW
485485
qgis->unregisterMapLayerPropertiesFactory( factory );
486486
}
487487

488+
void QgisAppInterface::registerCustomDropHandler( QgsCustomDropHandler *handler )
489+
{
490+
qgis->registerCustomDropHandler( handler );
491+
}
492+
493+
void QgisAppInterface::unregisterCustomDropHandler( QgsCustomDropHandler *handler )
494+
{
495+
qgis->unregisterCustomDropHandler( handler );
496+
}
497+
488498
//! Menus
489499
QMenu *QgisAppInterface::projectMenu() { return qgis->projectMenu(); }
490500
QMenu *QgisAppInterface::editMenu() { return qgis->editMenu(); }

src/app/qgisappinterface.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,18 @@ class APP_EXPORT QgisAppInterface : public QgisInterface
301301
*/
302302
virtual void unregisterMapLayerConfigWidgetFactory( QgsMapLayerConfigWidgetFactory* factory ) override;
303303

304+
/** Register a new custom drop handler.
305+
* @note added in QGIS 3.0
306+
* @note Ownership of the factory is not transferred, and the factory must
307+
* be unregistered when plugin is unloaded.
308+
* @see unregisterCustomDropHandler() */
309+
virtual void registerCustomDropHandler( QgsCustomDropHandler* handler ) override;
310+
311+
/** Unregister a previously registered custom drop handler.
312+
* @note added in QGIS 3.0
313+
* @see registerCustomDropHandler() */
314+
virtual void unregisterCustomDropHandler( QgsCustomDropHandler* handler ) override;
315+
304316
/** Accessors for inserting items into menus and toolbars.
305317
* An item can be inserted before any existing action.
306318
*/

src/app/qgsbrowserdockwidget.cpp

Lines changed: 3 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -516,26 +516,9 @@ void QgsBrowserDockWidget::addLayer( QgsLayerItem *layerItem )
516516
if ( !layerItem )
517517
return;
518518

519-
QString uri = QgisApp::instance()->crsAndFormatAdjustedLayerUri( layerItem->uri(), layerItem->supportedCrs(), layerItem->supportedFormats() );
520-
if ( uri.isEmpty() )
521-
return;
522-
523-
QgsMapLayer::LayerType type = layerItem->mapLayerType();
524-
QString providerKey = layerItem->providerKey();
525-
526-
QgsDebugMsg( providerKey + " : " + uri );
527-
if ( type == QgsMapLayer::VectorLayer )
528-
{
529-
QgisApp::instance()->addVectorLayer( uri, layerItem->layerName(), providerKey );
530-
}
531-
if ( type == QgsMapLayer::RasterLayer )
532-
{
533-
QgisApp::instance()->addRasterLayer( uri, layerItem->layerName(), providerKey );
534-
}
535-
if ( type == QgsMapLayer::PluginLayer )
536-
{
537-
QgisApp::instance()->addPluginLayer( uri, layerItem->layerName(), providerKey );
538-
}
519+
QgsMimeDataUtils::UriList list;
520+
list << layerItem->mimeUri();
521+
QgisApp::instance()->handleDropUriList( list );
539522
}
540523

541524
void QgsBrowserDockWidget::addLayerAtIndex( const QModelIndex& index )

src/core/qgsbrowsermodel.cpp

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -186,10 +186,9 @@ Qt::ItemFlags QgsBrowserModel::flags( const QModelIndex & index ) const
186186
Qt::ItemFlags flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable;
187187

188188
QgsDataItem* ptr = reinterpret_cast< QgsDataItem* >( index.internalPointer() );
189-
if ( ptr->type() == QgsDataItem::Layer || ptr->type() == QgsDataItem::Project )
190-
{
189+
if ( ptr->hasDragEnabled() )
191190
flags |= Qt::ItemIsDragEnabled;
192-
}
191+
193192
if ( ptr->acceptDrop() )
194193
flags |= Qt::ItemIsDropEnabled;
195194
return flags;
@@ -461,9 +460,9 @@ QMimeData * QgsBrowserModel::mimeData( const QModelIndexList &indexes ) const
461460
return mimeData;
462461
}
463462

464-
if ( ptr->type() != QgsDataItem::Layer ) continue;
465-
QgsLayerItem *layer = static_cast< QgsLayerItem* >( ptr );
466-
lst.append( QgsMimeDataUtils::Uri( layer ) );
463+
QgsMimeDataUtils::Uri uri = ptr->mimeUri();
464+
if ( uri.isValid() )
465+
lst.append( uri );
467466
}
468467
}
469468
return QgsMimeDataUtils::encodeUriList( lst );

0 commit comments

Comments
 (0)