Skip to content

Commit

Permalink
Added move to bottom in layertreeview context menu
Browse files Browse the repository at this point in the history
  • Loading branch information
uclaros authored and nyalldawson committed Apr 4, 2020
1 parent 9d09998 commit 6aca7b6
Show file tree
Hide file tree
Showing 8 changed files with 240 additions and 14 deletions.
7 changes: 7 additions & 0 deletions python/core/auto_generated/layertree/qgslayertreenode.sip.in
Expand Up @@ -208,6 +208,13 @@ Returns a list of any checked layers which belong to this node or its
children.

.. versionadded:: 3.0
%End

int depth() const;
%Docstring
Returns the depth of this node, i.e. the number of it's ancestors

.. versionadded:: 3.14
%End

bool isExpanded() const;
Expand Down
Expand Up @@ -80,6 +80,14 @@ Action to zoom to selected features of a vector layer
.. seealso:: :py:func:`moveToTop`

.. versionadded:: 3.2
%End

QAction *actionMoveToBottom( QObject *parent = 0 ) /Factory/;
%Docstring

.. seealso:: :py:func:`moveToBottom`

.. versionadded:: 3.14
%End
QAction *actionGroupSelected( QObject *parent = 0 ) /Factory/;

Expand Down Expand Up @@ -139,6 +147,14 @@ Moves selected layer(s) and/or group(s) to the top of the layer panel
or the top of the group if the layer/group is placed within a group.

.. versionadded:: 3.2
%End

void moveToBottom();
%Docstring
Moves selected layer(s) and/or group(s) to the bottom of the layer panel
or the bottom of the group if the layer/group is placed within a group.

.. versionadded:: 3.14
%End
void groupSelected();

Expand Down
10 changes: 10 additions & 0 deletions src/app/qgsapplayertreeviewmenuprovider.cpp
Expand Up @@ -117,6 +117,11 @@ QMenu *QgsAppLayerTreeViewMenuProvider::createContextMenu()
menu->addAction( actions->actionMoveToTop( menu ) );
}

if ( !( mView->selectedNodes( true ).count() == 1 && idx.row() == idx.model()->rowCount() - 1 ) )
{
menu->addAction( actions->actionMoveToBottom( menu ) );
}

menu->addSeparator();

if ( mView->selectedNodes( true ).count() >= 2 )
Expand Down Expand Up @@ -204,6 +209,11 @@ QMenu *QgsAppLayerTreeViewMenuProvider::createContextMenu()
menu->addAction( actions->actionMoveToTop( menu ) );
}

if ( !( mView->selectedNodes( true ).count() == 1 && idx.row() == idx.model()->rowCount() - 1 ) )
{
menu->addAction( actions->actionMoveToBottom( menu ) );
}

QAction *checkAll = actions->actionCheckAndAllParents( menu );
if ( checkAll )
menu->addAction( checkAll );
Expand Down
12 changes: 12 additions & 0 deletions src/core/layertree/qgslayertreenode.cpp
Expand Up @@ -155,6 +155,18 @@ QList<QgsMapLayer *> QgsLayerTreeNode::checkedLayers() const
return layers;
}

int QgsLayerTreeNode::depth() const
{
int depth = 0;
QgsLayerTreeNode *node = mParent;
while ( node )
{
node = node->parent();
++depth;
}
return depth;
}

void QgsLayerTreeNode::setExpanded( bool expanded )
{
if ( mExpanded == expanded )
Expand Down
6 changes: 6 additions & 0 deletions src/core/layertree/qgslayertreenode.h
Expand Up @@ -208,6 +208,12 @@ class CORE_EXPORT QgsLayerTreeNode : public QObject
*/
QList< QgsMapLayer * > checkedLayers() const;

/**
* Returns the depth of this node, i.e. the number of it's ancestors
* \since QGIS 3.14
*/
int depth() const;

//! Returns whether the node should be shown as expanded or collapsed in GUI
bool isExpanded() const;
//! Sets whether the node should be shown as expanded or collapsed in GUI
Expand Down
48 changes: 34 additions & 14 deletions src/gui/layertree/qgslayertreeviewdefaultactions.cpp
Expand Up @@ -138,6 +138,13 @@ QAction *QgsLayerTreeViewDefaultActions::actionMoveToTop( QObject *parent )
return a;
}

QAction *QgsLayerTreeViewDefaultActions::actionMoveToBottom( QObject *parent )
{
QAction *a = new QAction( tr( "Move to &Bottom" ), parent );
connect( a, &QAction::triggered, this, &QgsLayerTreeViewDefaultActions::moveToBottom );
return a;
}

QAction *QgsLayerTreeViewDefaultActions::actionGroupSelected( QObject *parent )
{
QAction *a = new QAction( tr( "&Group Selected" ), parent );
Expand Down Expand Up @@ -429,27 +436,40 @@ void QgsLayerTreeViewDefaultActions::moveOutOfGroup()

void QgsLayerTreeViewDefaultActions::moveToTop()
{
QHash <QgsLayerTreeGroup *, int> groupInsertIdx;
int insertIdx;
const QList< QgsLayerTreeNode * > selectedNodes = mView->selectedNodes();
for ( QgsLayerTreeNode *n : selectedNodes )
QList< QgsLayerTreeNode * > selectedNodes = mView->selectedNodes();
// sort the nodes by depth first to avoid moving a group before it's contents
std::stable_sort( selectedNodes.begin(), selectedNodes.end(), []( const QgsLayerTreeNode * a, const QgsLayerTreeNode * b )
{
return a->depth() < b->depth();
} );
for ( auto it = selectedNodes.rbegin(); it < selectedNodes.rend(); ++it )
{
QgsLayerTreeGroup *parentGroup = qobject_cast<QgsLayerTreeGroup *>( ( *it )->parent() );
QgsLayerTreeNode *clonedNode = ( *it )->clone();
parentGroup->insertChildNode( 0, clonedNode );
parentGroup->removeChildNode( ( *it ) );
}
}


void QgsLayerTreeViewDefaultActions::moveToBottom()
{
QList< QgsLayerTreeNode * > selectedNodes = mView->selectedNodes();
// sort the nodes by depth first to avoid moving a group before it's contents
std::stable_sort( selectedNodes.begin(), selectedNodes.end(), []( const QgsLayerTreeNode * a, const QgsLayerTreeNode * b )
{
return a->depth() > b->depth();
} );
for ( QgsLayerTreeNode *n : qgis::as_const( selectedNodes ) )
{
QgsLayerTreeGroup *parentGroup = qobject_cast<QgsLayerTreeGroup *>( n->parent() );
QgsLayerTreeNode *clonedNode = n->clone();
if ( groupInsertIdx.contains( parentGroup ) )
{
insertIdx = groupInsertIdx.value( parentGroup );
}
else
{
insertIdx = 0;
}
parentGroup->insertChildNode( insertIdx, clonedNode );
parentGroup->insertChildNode( -1, clonedNode );
parentGroup->removeChildNode( n );
groupInsertIdx.insert( parentGroup, insertIdx + 1 );
}
}


void QgsLayerTreeViewDefaultActions::groupSelected()
{
const QList<QgsLayerTreeNode *> nodes = mView->selectedNodes( true );
Expand Down
13 changes: 13 additions & 0 deletions src/gui/layertree/qgslayertreeviewdefaultactions.h
Expand Up @@ -82,6 +82,12 @@ class GUI_EXPORT QgsLayerTreeViewDefaultActions : public QObject
* \since QGIS 3.2
*/
QAction *actionMoveToTop( QObject *parent = nullptr ) SIP_FACTORY;

/**
* \see moveToBottom()
* \since QGIS 3.14
*/
QAction *actionMoveToBottom( QObject *parent = nullptr ) SIP_FACTORY;
QAction *actionGroupSelected( QObject *parent = nullptr ) SIP_FACTORY;

/**
Expand Down Expand Up @@ -133,6 +139,13 @@ class GUI_EXPORT QgsLayerTreeViewDefaultActions : public QObject
* \since QGIS 3.2
*/
void moveToTop();

/**
* Moves selected layer(s) and/or group(s) to the bottom of the layer panel
* or the bottom of the group if the layer/group is placed within a group.
* \since QGIS 3.14
*/
void moveToBottom();
void groupSelected();

/**
Expand Down
142 changes: 142 additions & 0 deletions tests/src/python/test_qgslayertreeview.py
Expand Up @@ -211,6 +211,148 @@ def testMoveToTopActionEmbeddedGroup(self):
groupname + '-' + self.layer4.name(),
])

def testMoveToTopActionLayerAndGroup(self):
"""Test move to top action for a group and it's layer simultaneously"""
view = QgsLayerTreeView()
group = self.project.layerTreeRoot().addGroup("embeddedgroup")
group.addLayer(self.layer4)
group.addLayer(self.layer5)
groupname = group.name()
view.setModel(self.model)
actions = QgsLayerTreeViewDefaultActions(view)
self.assertEqual(self.nodeOrder(self.project.layerTreeRoot().children()), [
self.layer.name(),
self.layer2.name(),
self.layer3.name(),
groupname,
groupname + '-' + self.layer4.name(),
groupname + '-' + self.layer5.name(),
])

selectionMode = view.selectionMode()
view.setSelectionMode(QgsLayerTreeView.MultiSelection)
nodeLayerIndex = self.model.node2index(group)
view.setCurrentIndex(nodeLayerIndex)
view.setCurrentLayer(self.layer5)
view.setSelectionMode(selectionMode)
movetotop = actions.actionMoveToTop()
movetotop.trigger()
self.assertEqual(self.nodeOrder(self.project.layerTreeRoot().children()), [
groupname,
groupname + '-' + self.layer5.name(),
groupname + '-' + self.layer4.name(),
self.layer.name(),
self.layer2.name(),
self.layer3.name(),
])

def testMoveToBottomActionLayer(self):
"""Test move to bottom action on layer"""
view = QgsLayerTreeView()
view.setModel(self.model)
actions = QgsLayerTreeViewDefaultActions(view)
self.assertEqual(self.project.layerTreeRoot().layerOrder(), [self.layer, self.layer2, self.layer3])
view.setCurrentLayer(self.layer)
movetobottom = actions.actionMoveToBottom()
movetobottom.trigger()
self.assertEqual(self.project.layerTreeRoot().layerOrder(), [self.layer2, self.layer3, self.layer])

def testMoveToBottomActionGroup(self):
"""Test move to bottom action on group"""
view = QgsLayerTreeView()
group = self.project.layerTreeRoot().insertGroup(0, "embeddedgroup")
group.addLayer(self.layer4)
group.addLayer(self.layer5)
groupname = group.name()
view.setModel(self.model)
actions = QgsLayerTreeViewDefaultActions(view)
self.assertEqual(self.nodeOrder(self.project.layerTreeRoot().children()), [
groupname,
groupname + '-' + self.layer4.name(),
groupname + '-' + self.layer5.name(),
self.layer.name(),
self.layer2.name(),
self.layer3.name(),
])

nodeLayerIndex = self.model.node2index(group)
view.setCurrentIndex(nodeLayerIndex)
movetobottom = actions.actionMoveToBottom()
movetobottom.trigger()
self.assertEqual(self.nodeOrder(self.project.layerTreeRoot().children()), [
self.layer.name(),
self.layer2.name(),
self.layer3.name(),
groupname,
groupname + '-' + self.layer4.name(),
groupname + '-' + self.layer5.name(),
])

def testMoveToBottomActionEmbeddedGroup(self):
"""Test move to bottom action on embeddedgroup layer"""
view = QgsLayerTreeView()
group = self.project.layerTreeRoot().addGroup("embeddedgroup")
group.addLayer(self.layer4)
group.addLayer(self.layer5)
groupname = group.name()
view.setModel(self.model)
actions = QgsLayerTreeViewDefaultActions(view)
self.assertEqual(self.nodeOrder(self.project.layerTreeRoot().children()), [
self.layer.name(),
self.layer2.name(),
self.layer3.name(),
groupname,
groupname + '-' + self.layer4.name(),
groupname + '-' + self.layer5.name(),
])

view.setCurrentLayer(self.layer4)
movetobottom = actions.actionMoveToBottom()
movetobottom.trigger()
self.assertEqual(self.nodeOrder(self.project.layerTreeRoot().children()), [
self.layer.name(),
self.layer2.name(),
self.layer3.name(),
groupname,
groupname + '-' + self.layer5.name(),
groupname + '-' + self.layer4.name(),
])

def testMoveToBottomActionLayerAndGroup(self):
"""Test move to top action for a group and it's layer simultaneously"""
view = QgsLayerTreeView()
group = self.project.layerTreeRoot().insertGroup(0, "embeddedgroup")
group.addLayer(self.layer4)
group.addLayer(self.layer5)
groupname = group.name()
view.setModel(self.model)
actions = QgsLayerTreeViewDefaultActions(view)
self.assertEqual(self.nodeOrder(self.project.layerTreeRoot().children()), [
groupname,
groupname + '-' + self.layer4.name(),
groupname + '-' + self.layer5.name(),
self.layer.name(),
self.layer2.name(),
self.layer3.name(),
])

selectionMode = view.selectionMode()
view.setSelectionMode(QgsLayerTreeView.MultiSelection)
nodeLayerIndex = self.model.node2index(group)
view.setCurrentIndex(nodeLayerIndex)
view.setCurrentLayer(self.layer4)
view.setSelectionMode(selectionMode)
movetobottom = actions.actionMoveToBottom()
movetobottom.trigger()
self.assertEqual(self.nodeOrder(self.project.layerTreeRoot().children()), [
self.layer.name(),
self.layer2.name(),
self.layer3.name(),
groupname,
groupname + '-' + self.layer5.name(),
groupname + '-' + self.layer4.name(),
])

def testSetLayerVisible(self):
view = QgsLayerTreeView()
view.setModel(self.model)
Expand Down

0 comments on commit 6aca7b6

Please sign in to comment.