Skip to content

Commit

Permalink
Merge pull request #35382 from 3nids/dnd-across-qgis-instances
Browse files Browse the repository at this point in the history
[FEATURE] allow to drag'n'drop layer across several QGIS instances
  • Loading branch information
3nids committed May 6, 2020
2 parents b1873a4 + c6ee633 commit 37efb4d
Show file tree
Hide file tree
Showing 7 changed files with 121 additions and 31 deletions.
9 changes: 9 additions & 0 deletions python/core/auto_generated/layertree/qgslayertreemodel.sip.in
Expand Up @@ -319,6 +319,15 @@ displays.
``standardSize`` should be set to a standard icon size, e.g. 16, 24, 48, etc.

.. versionadded:: 3.6
%End

signals:

void messageEmitted( const QString &message, Qgis::MessageLevel level = Qgis::Info, int duration = 5 );
%Docstring
Emits a message than can be displayed to the user in a GUI class

.. versionadded:: 3.14
%End

protected slots:
Expand Down
8 changes: 8 additions & 0 deletions python/gui/auto_generated/layertree/qgslayertreeview.sip.in
Expand Up @@ -9,6 +9,7 @@




class QgsLayerTreeView : QTreeView
{
%Docstring
Expand Down Expand Up @@ -211,6 +212,13 @@ Set width of contextual menu mark, at right of layer node items.
.. seealso:: :py:func:`layerMarkWidth`

.. versionadded:: 3.8
%End

void setMessageBar( QgsMessageBar *messageBar );
%Docstring
Set the message bar to display messages from the layer tree

.. versionadded:: 3.14
%End

signals:
Expand Down
3 changes: 3 additions & 0 deletions src/app/qgisapp.cpp
Expand Up @@ -1687,6 +1687,7 @@ QgisApp::QgisApp()
mUndoWidget = new QgsUndoWidget( nullptr, mMapCanvas );
mUserInputDockWidget = new QgsUserInputWidget( this );
mInfoBar = new QgsMessageBar( centralWidget() );
mLayerTreeView->setMessageBar( mInfoBar );
mAdvancedDigitizingDockWidget = new QgsAdvancedDigitizingDockWidget( mMapCanvas, this );
mPanelMenu = new QMenu( this );
mProgressBar = new QProgressBar( this );
Expand Down Expand Up @@ -4590,6 +4591,8 @@ void QgisApp::initLayerTreeView()
model->setAutoCollapseLegendNodes( 10 );

mLayerTreeView->setModel( model );
mLayerTreeView->setMessageBar( mInfoBar );

mLayerTreeView->setMenuProvider( new QgsAppLayerTreeViewMenuProvider( mLayerTreeView, mMapCanvas ) );
new QgsLayerTreeViewFilterIndicatorProvider( mLayerTreeView ); // gets parented to the layer view
new QgsLayerTreeViewEmbeddedIndicatorProvider( mLayerTreeView ); // gets parented to the layer view
Expand Down
91 changes: 60 additions & 31 deletions src/core/layertree/qgslayertreemodel.cpp
Expand Up @@ -13,17 +13,16 @@
* *
***************************************************************************/

#include <QMimeData>
#include <QTextStream>

#include "qgslayertreemodel.h"

#include "qgslayertree.h"
#include "qgslayertreeutils.h"
#include "qgslayertreemodellegendnode.h"
#include "qgsproject.h"
#include "qgsapplication.h"

#include <QMimeData>
#include <QTextStream>

#include "qgsdataitem.h"
#include "qgsmaphittest.h"
#include "qgsmaplayer.h"
Expand All @@ -34,6 +33,8 @@
#include "qgsrenderer.h"
#include "qgssymbollayerutils.h"
#include "qgsvectorlayer.h"
#include "qgslayerdefinition.h"


QgsLayerTreeModel::QgsLayerTreeModel( QgsLayerTree *rootNode, QObject *parent )
: QAbstractItemModel( parent )
Expand Down Expand Up @@ -1052,15 +1053,25 @@ QMimeData *QgsLayerTreeModel::mimeData( const QModelIndexList &indexes ) const

QMimeData *mimeData = new QMimeData();

QDomDocument doc;
QDomElement rootElem = doc.createElement( QStringLiteral( "layer_tree_model_data" ) );
QDomDocument layerTreeDoc;
QDomElement rootLayerTreeElem = layerTreeDoc.createElement( QStringLiteral( "layer_tree_model_data" ) );

for ( QgsLayerTreeNode *node : qgis::as_const( nodesFinal ) )
node->writeXml( rootElem, QgsReadWriteContext() );
doc.appendChild( rootElem );
QString txt = doc.toString();
{
node->writeXml( rootLayerTreeElem, QgsReadWriteContext() );
}
layerTreeDoc.appendChild( rootLayerTreeElem );

mimeData->setData( QStringLiteral( "application/qgis.layertreemodeldata" ), txt.toUtf8() );
QString errorMessage;
QgsReadWriteContext readWriteContext;
QDomDocument layerDefinitionsDoc( QStringLiteral( "qgis-layer-definition" ) );
QgsLayerDefinition::exportLayerDefinition( layerDefinitionsDoc, nodesFinal, errorMessage, QgsReadWriteContext() );

QString txt = layerDefinitionsDoc.toString();

mimeData->setData( QStringLiteral( "application/qgis.layertreemodeldata" ), layerTreeDoc.toString().toUtf8() );
mimeData->setData( QStringLiteral( "application/qgis.application.pid" ), QString::number( QCoreApplication::applicationPid() ).toUtf8() );
mimeData->setData( QStringLiteral( "application/qgis.layertree.layerdefinitions" ), txt.toUtf8() );
mimeData->setData( QStringLiteral( "application/x-vnd.qgis.qgis.uri" ), QgsMimeDataUtils::layerTreeNodesToUriList( nodesFinal ) );

return mimeData;
Expand All @@ -1081,36 +1092,54 @@ bool QgsLayerTreeModel::dropMimeData( const QMimeData *data, Qt::DropAction acti
if ( !QgsLayerTree::isGroup( nodeParent ) )
return false;

QByteArray encodedData = data->data( QStringLiteral( "application/qgis.layertreemodeldata" ) );
if ( parent.isValid() && row == -1 )
row = 0; // if dropped directly onto group item, insert at first position

QDomDocument doc;
if ( !doc.setContent( QString::fromUtf8( encodedData ) ) )
return false;
// if we are coming from another QGIS instance, we need to add the layers too
bool ok = false;
// the application pid is only provided from QGIS 3.14, so do not check to OK before defaulting to moving in the legend
int qgisPid = data->data( QStringLiteral( "application/qgis.application.pid" ) ).toInt( &ok );

QDomElement rootElem = doc.documentElement();
if ( rootElem.tagName() != QLatin1String( "layer_tree_model_data" ) )
return false;
if ( ok && qgisPid != QString::number( QCoreApplication::applicationPid() ) )
{
QByteArray encodedLayerDefinitionData = data->data( QStringLiteral( "application/qgis.layertree.layerdefinitions" ) );
QDomDocument layerDefinitionDoc;
if ( !layerDefinitionDoc.setContent( QString::fromUtf8( encodedLayerDefinitionData ) ) )
return false;
QgsReadWriteContext context;
QString errorMessage;
QgsLayerDefinition::loadLayerDefinition( layerDefinitionDoc, QgsProject::instance(), QgsLayerTree::toGroup( nodeParent ), errorMessage, context );
emit messageEmitted( tr( "New layers added from another QGIS instance" ) );
}
else
{
QByteArray encodedLayerTreeData = data->data( QStringLiteral( "application/qgis.layertreemodeldata" ) );

QList<QgsLayerTreeNode *> nodes;
QDomDocument layerTreeDoc;
if ( !layerTreeDoc.setContent( QString::fromUtf8( encodedLayerTreeData ) ) )
return false;

QDomElement elem = rootElem.firstChildElement();
while ( !elem.isNull() )
{
QgsLayerTreeNode *node = QgsLayerTreeNode::readXml( elem, QgsProject::instance() );
if ( node )
nodes << node;
QDomElement rootLayerTreeElem = layerTreeDoc.documentElement();
if ( rootLayerTreeElem.tagName() != QLatin1String( "layer_tree_model_data" ) )
return false;

elem = elem.nextSiblingElement();
}
QList<QgsLayerTreeNode *> nodes;

if ( nodes.isEmpty() )
return false;
QDomElement elem = rootLayerTreeElem.firstChildElement();
while ( !elem.isNull() )
{
QgsLayerTreeNode *node = QgsLayerTreeNode::readXml( elem, QgsProject::instance() );
if ( node )
nodes << node;

if ( parent.isValid() && row == -1 )
row = 0; // if dropped directly onto group item, insert at first position
elem = elem.nextSiblingElement();
}

QgsLayerTree::toGroup( nodeParent )->insertChildNodes( row, nodes );
if ( nodes.isEmpty() )
return false;

QgsLayerTree::toGroup( nodeParent )->insertChildNodes( row, nodes );
}
return true;
}

Expand Down
8 changes: 8 additions & 0 deletions src/core/layertree/qgslayertreemodel.h
Expand Up @@ -286,6 +286,14 @@ class CORE_EXPORT QgsLayerTreeModel : public QAbstractItemModel
*/
static int scaleIconSize( int standardSize );

signals:

/**
* Emits a message than can be displayed to the user in a GUI class
* \since QGIS 3.14
*/
void messageEmitted( const QString &message, Qgis::MessageLevel level = Qgis::Info, int duration = 5 );

protected slots:
void nodeWillAddChildren( QgsLayerTreeNode *node, int indexFrom, int indexTo );
void nodeAddedChildren( QgsLayerTreeNode *node, int indexFrom, int indexTo );
Expand Down
22 changes: 22 additions & 0 deletions src/gui/layertree/qgslayertreeview.cpp
Expand Up @@ -22,6 +22,8 @@
#include "qgslayertreeutils.h"
#include "qgslayertreeviewdefaultactions.h"
#include "qgsmaplayer.h"
#include "qgsmessagebar.h"

#include "qgsgui.h"

#include <QMenu>
Expand Down Expand Up @@ -77,6 +79,12 @@ void QgsLayerTreeView::setModel( QAbstractItemModel *model )
connect( model, &QAbstractItemModel::rowsInserted, this, &QgsLayerTreeView::modelRowsInserted );
connect( model, &QAbstractItemModel::rowsRemoved, this, &QgsLayerTreeView::modelRowsRemoved );

if ( mMessageBar )
connect( layerTreeModel(), &QgsLayerTreeModel::messageEmitted,
[ = ]( const QString & message, Qgis::MessageLevel level = Qgis::Info, int duration = 5 )
{mMessageBar->pushMessage( message, level, duration );}
);

QTreeView::setModel( model );

connect( layerTreeModel()->rootGroup(), &QgsLayerTreeNode::expandedChanged, this, &QgsLayerTreeView::onExpandedChanged );
Expand Down Expand Up @@ -490,6 +498,20 @@ void QgsLayerTreeView::collapseAllNodes()
collapseAll();
}

void QgsLayerTreeView::setMessageBar( QgsMessageBar *messageBar )
{
if ( mMessageBar == messageBar )
return;

mMessageBar = messageBar;

if ( mMessageBar )
connect( layerTreeModel(), &QgsLayerTreeModel::messageEmitted,
[ = ]( const QString & message, Qgis::MessageLevel level = Qgis::Info, int duration = 5 )
{mMessageBar->pushMessage( message, level, duration );}
);
}

void QgsLayerTreeView::mouseReleaseEvent( QMouseEvent *event )
{
// we need to keep last mouse position in order to know whether to emit an indicator's clicked() signal
Expand Down
11 changes: 11 additions & 0 deletions src/gui/layertree/qgslayertreeview.h
Expand Up @@ -29,6 +29,8 @@ class QgsLayerTreeViewDefaultActions;
class QgsLayerTreeViewIndicator;
class QgsLayerTreeViewMenuProvider;
class QgsMapLayer;
class QgsMessageBar;


/**
* \ingroup gui
Expand Down Expand Up @@ -206,6 +208,12 @@ class GUI_EXPORT QgsLayerTreeView : public QTreeView
*/
void setLayerMarkWidth( int width ) { mLayerMarkWidth = width; }

/**
* Set the message bar to display messages from the layer tree
* \since QGIS 3.14
*/
void setMessageBar( QgsMessageBar *messageBar );

signals:
//! Emitted when a current layer is changed
void currentLayerChanged( QgsMapLayer *layer );
Expand Down Expand Up @@ -253,6 +261,9 @@ class GUI_EXPORT QgsLayerTreeView : public QTreeView
//! Width of contextual menu mark for layer nodes
int mLayerMarkWidth;

private:
QgsMessageBar *mMessageBar = nullptr;

// friend so it can access viewOptions() method and mLastReleaseMousePos without making them public
friend class QgsLayerTreeViewItemDelegate;
};
Expand Down

0 comments on commit 37efb4d

Please sign in to comment.