pyqgis – Preserving QgsLayerTreeViewIndicator When Moving Node in Layer Tree

iconlayerspyqgisqgslayertree

I am using the script below to display an icon next to my layer in the layer tree view, which works fine. However, when a user moves the position of that node, the indicator disappears.

layer1 = QgsVectorLayer("Point?crs=EPSG:4326", "my layer", "memory")
QgsProject.instance().addMapLayer(layer1, False)

root = QgsProject.instance().layerTreeRoot()
node_layer1 = root.addLayer(layer1)

indicator = QgsLayerTreeViewIndicator(iface.layerTreeView())
indicator.setToolTip('Test')
indicator.setIcon(QIcon(':/images/themes/default/mActionIdentify.svg'))
indicator.clicked.connect(lambda: print('indicator clicked'))

iface.layerTreeView().addIndicator(node_layer1, indicator)

How would I preserve the indicator and the connected action, so that it can be triggered even after moving the node?

Some context on what I additionally tried so far:

I tried to listen for the QAbstractItemModel.rowsMoved signal, to identify when the node is moved and then again re-add the indicator afterwards.

But it seems that only this specific signal is not emitted by QgsLayerTreeModel.

Another thing I tried is listening to the rowsInserted signals to detect if my created node has been inserted at a different position, in order to then again re-add the indicator. But that also does not work, because on moving the node the original object gets deleted. So if I try to add the indicator with iface.layerTreeView().addIndicator(node_layer1, indicator) it complains about a

RuntimeError: wrapped C/C++ object of type QgsLayerTreeLayer has been
deleted

Best Answer

I was able to get it working with the code below:

layer1 = QgsVectorLayer("Point?crs=EPSG:4326", "my layer", "memory")
QgsProject.instance().addMapLayer(layer1, False)

root = QgsProject.instance().layerTreeRoot()
node = root.addLayer(layer1)

indicator = QgsLayerTreeViewIndicator(iface.layerTreeView())
indicator.setToolTip('Test')
indicator.setIcon(QIcon(':/images/themes/default/mActionIdentify.svg'))
indicator.clicked.connect(lambda: print('indicator clicked'))

view = iface.layerTreeView()

view.addIndicator(node, indicator)

def rows_inserted(parent, first, last):
    nodes = root.findLayers()
    for n in nodes:
        if n.layer().id() == layer1.id():
            if not view.indicators(n):
                view.addIndicator(n, indicator)

conn = view.layerTreeModel().rowsInserted.connect(rows_inserted)

After removing layer, clean up by disconnecting signal/slot and deleting indicator object:

QObject.disconnect(conn)
del indicator

Screencast shows this working:

enter image description here

Based very loosely on source code in qgslayertreeviewindicatorprovider.cpp which would probably take care of this nicely but does not seem to be exposed in the Python API bindings.