1 /***************************************************************************
2   qgslayertreeregistrybridge.cpp
3   --------------------------------------
4   Date                 : May 2014
5   Copyright            : (C) 2014 by Martin Dobias
6   Email                : wonder dot sk at gmail dot com
7  ***************************************************************************
8  *                                                                         *
9  *   This program is free software; you can redistribute it and/or modify  *
10  *   it under the terms of the GNU General Public License as published by  *
11  *   the Free Software Foundation; either version 2 of the License, or     *
12  *   (at your option) any later version.                                   *
13  *                                                                         *
14  ***************************************************************************/
15 
16 #include "qgslayertreeregistrybridge.h"
17 
18 #include "qgslayertree.h"
19 
20 #include "qgsproject.h"
21 #include "qgslogger.h"
22 
QgsLayerTreeRegistryBridge(QgsLayerTreeGroup * root,QgsProject * project,QObject * parent)23 QgsLayerTreeRegistryBridge::QgsLayerTreeRegistryBridge( QgsLayerTreeGroup *root, QgsProject *project, QObject *parent )
24   : QObject( parent )
25   , mRoot( root )
26   , mProject( project )
27   , mRegistryRemovingLayers( false )
28   , mEnabled( true )
29   , mNewLayersVisible( true )
30   , mInsertionPoint( root, 0 )
31 {
32   connect( mProject, &QgsProject::legendLayersAdded, this, &QgsLayerTreeRegistryBridge::layersAdded );
33   connect( mProject, static_cast < void ( QgsProject::* )( const QStringList & ) >( &QgsProject::layersWillBeRemoved ), this, &QgsLayerTreeRegistryBridge::layersWillBeRemoved );
34 
35   connect( mRoot, &QgsLayerTreeNode::willRemoveChildren, this, &QgsLayerTreeRegistryBridge::groupWillRemoveChildren );
36   connect( mRoot, &QgsLayerTreeNode::removedChildren, this, &QgsLayerTreeRegistryBridge::groupRemovedChildren );
37 }
38 
setLayerInsertionPoint(QgsLayerTreeGroup * parentGroup,int index)39 void QgsLayerTreeRegistryBridge::setLayerInsertionPoint( QgsLayerTreeGroup *parentGroup, int index )
40 {
41   mInsertionPoint.group = parentGroup;
42   mInsertionPoint.position = index;
43 }
44 
setLayerInsertionPoint(const InsertionPoint & insertionPoint)45 void QgsLayerTreeRegistryBridge::setLayerInsertionPoint( const InsertionPoint &insertionPoint )
46 {
47   mInsertionPoint = insertionPoint;
48 }
49 
layersAdded(const QList<QgsMapLayer * > & layers)50 void QgsLayerTreeRegistryBridge::layersAdded( const QList<QgsMapLayer *> &layers )
51 {
52   if ( !mEnabled )
53     return;
54 
55   QList<QgsLayerTreeNode *> nodes;
56   const auto constLayers = layers;
57   for ( QgsMapLayer *layer : constLayers )
58   {
59     QgsLayerTreeLayer *nodeLayer = new QgsLayerTreeLayer( layer );
60     nodeLayer->setItemVisibilityChecked( mNewLayersVisible );
61 
62     nodes << nodeLayer;
63 
64     // check whether the layer is marked as embedded
65     QString projectFile = mProject->layerIsEmbedded( nodeLayer->layerId() );
66     if ( !projectFile.isEmpty() )
67     {
68       nodeLayer->setCustomProperty( QStringLiteral( "embedded" ), 1 );
69       nodeLayer->setCustomProperty( QStringLiteral( "embedded_project" ), projectFile );
70     }
71   }
72 
73   // add new layers to the right place
74   mInsertionPoint.group->insertChildNodes( mInsertionPoint.position, nodes );
75 
76   // tell other components that layers have been added - this signal is used in QGIS to auto-select the first layer
77   emit addedLayersToLayerTree( layers );
78 }
79 
layersWillBeRemoved(const QStringList & layerIds)80 void QgsLayerTreeRegistryBridge::layersWillBeRemoved( const QStringList &layerIds )
81 {
82   QgsDebugMsgLevel( QStringLiteral( "%1 layers will be removed, enabled:%2" ).arg( layerIds.count() ).arg( mEnabled ), 4 );
83 
84   if ( !mEnabled )
85     return;
86 
87   // when we start removing child nodes, the bridge would try to remove those layers from
88   // the registry _again_ in groupRemovedChildren() - this prevents it
89   mRegistryRemovingLayers = true;
90 
91   const auto constLayerIds = layerIds;
92   for ( const QString &layerId : constLayerIds )
93   {
94     QgsLayerTreeLayer *nodeLayer = mRoot->findLayer( layerId );
95     if ( nodeLayer )
96       qobject_cast<QgsLayerTreeGroup *>( nodeLayer->parent() )->removeChildNode( nodeLayer );
97   }
98 
99   mRegistryRemovingLayers = false;
100 }
101 
102 
_collectLayerIdsInGroup(QgsLayerTreeGroup * group,int indexFrom,int indexTo,QStringList & lst)103 static void _collectLayerIdsInGroup( QgsLayerTreeGroup *group, int indexFrom, int indexTo, QStringList &lst )
104 {
105   for ( int i = indexFrom; i <= indexTo; ++i )
106   {
107     QgsLayerTreeNode *child = group->children().at( i );
108     if ( QgsLayerTree::isLayer( child ) )
109     {
110       lst << QgsLayerTree::toLayer( child )->layerId();
111     }
112     else if ( QgsLayerTree::isGroup( child ) )
113     {
114       _collectLayerIdsInGroup( QgsLayerTree::toGroup( child ), 0, child->children().count() - 1, lst );
115     }
116   }
117 }
118 
groupWillRemoveChildren(QgsLayerTreeNode * node,int indexFrom,int indexTo)119 void QgsLayerTreeRegistryBridge::groupWillRemoveChildren( QgsLayerTreeNode *node, int indexFrom, int indexTo )
120 {
121   if ( mRegistryRemovingLayers )
122     return; // do not try to remove those layers again
123 
124   Q_ASSERT( mLayerIdsForRemoval.isEmpty() );
125 
126   Q_ASSERT( QgsLayerTree::isGroup( node ) );
127   QgsLayerTreeGroup *group = QgsLayerTree::toGroup( node );
128 
129   _collectLayerIdsInGroup( group, indexFrom, indexTo, mLayerIdsForRemoval );
130 }
131 
groupRemovedChildren()132 void QgsLayerTreeRegistryBridge::groupRemovedChildren()
133 {
134   if ( mRegistryRemovingLayers )
135     return; // do not try to remove those layers again
136 
137   // remove only those that really do not exist in the tree
138   // (ignores layers that were dragged'n'dropped: 1. drop new 2. remove old)
139   QStringList toRemove;
140   const auto constMLayerIdsForRemoval = mLayerIdsForRemoval;
141   for ( const QString &layerId : constMLayerIdsForRemoval )
142     if ( !mRoot->findLayer( layerId ) )
143       toRemove << layerId;
144   mLayerIdsForRemoval.clear();
145 
146   QgsDebugMsgLevel( QStringLiteral( "%1 layers will be removed" ).arg( toRemove.count() ), 4 );
147 
148   // delay the removal of layers from the registry. There may be other slots connected to map layer registry's signals
149   // that might disrupt the execution flow - e.g. a processEvents() call may force update of layer tree view with
150   // semi-broken tree model
151   QMetaObject::invokeMethod( this, "removeLayersFromRegistry", Qt::QueuedConnection, Q_ARG( QStringList, toRemove ) );
152 }
153 
removeLayersFromRegistry(const QStringList & layerIds)154 void QgsLayerTreeRegistryBridge::removeLayersFromRegistry( const QStringList &layerIds )
155 {
156   mProject->removeMapLayers( layerIds );
157 }
158