Skip to content

Commit

Permalink
When taking main widget from QgsPanelWidgetStack, auto accept
Browse files Browse the repository at this point in the history
all open child panel widgets

Avoids the stack state becoming inconsistent because child
panel widgets from a different main panel are still present

And add unit tests for QgsPanelWidgetStack
  • Loading branch information
nyalldawson committed Oct 6, 2016
1 parent fbdc414 commit 37e3dd7
Show file tree
Hide file tree
Showing 5 changed files with 225 additions and 3 deletions.
14 changes: 14 additions & 0 deletions python/gui/qgspanelwidgetstack.sip
Expand Up @@ -53,15 +53,29 @@ class QgsPanelWidgetStack: public QWidget
*/
void clear();

/**
* Returns the panel currently shown in the stack.
* @note added in QGIS 3.0
*/
QgsPanelWidget* currentPanel();

public slots:
/**
* Accept the current active widget in the stack.
*
* Calls the panelAccepeted signal on the active widget.
* @see acceptAllPanels()
*/
void acceptCurrentPanel();

/**
* Accepts all panel widgets open in the stack in turn until until only the mainPanel()
* remains.
* @see acceptCurrentPanel();
* @note added in QGIS 3.0
*/
void acceptAllPanels();

/**
* Show a panel in the stack widget. Will connect to the panels showPanel event to handle
* nested panels. Auto switches the the given panel for the user.
Expand Down
31 changes: 28 additions & 3 deletions src/gui/qgspanelwidgetstack.cpp
Expand Up @@ -50,14 +50,17 @@ QgsPanelWidget *QgsPanelWidgetStack::mainPanel()

QgsPanelWidget *QgsPanelWidgetStack::takeMainPanel()
{
// clear out the current stack
acceptAllPanels();

QWidget* widget = mStackedWidget->widget( 0 );
mStackedWidget->removeWidget( widget );
return qobject_cast<QgsPanelWidget*>( widget );
}

void QgsPanelWidgetStack::clear()
{
for ( int i = mStackedWidget->count(); i >= 0; i-- )
for ( int i = mStackedWidget->count() - 1; i >= 0; i-- )
{
if ( QgsPanelWidget* panelWidget = qobject_cast<QgsPanelWidget*>( mStackedWidget->widget( i ) ) )
{
Expand All @@ -79,16 +82,38 @@ void QgsPanelWidgetStack::clear()
this->updateBreadcrumb();
}

QgsPanelWidget* QgsPanelWidgetStack::currentPanel()
{
return qobject_cast<QgsPanelWidget*>( mStackedWidget->currentWidget() );
}

void QgsPanelWidgetStack::acceptCurrentPanel()
{
// You can't accept the main panel.
if ( mStackedWidget->currentIndex() == 0 )
if ( mStackedWidget->currentIndex() <= 0 )
return;

QgsPanelWidget* widget = qobject_cast<QgsPanelWidget*>( mStackedWidget->currentWidget() );
QgsPanelWidget* widget = currentPanel();
widget->acceptPanel();
}

void QgsPanelWidgetStack::acceptAllPanels()
{
//avoid messy multiple redraws
setUpdatesEnabled( false );
mStackedWidget->setUpdatesEnabled( false );

for ( int i = mStackedWidget->count() - 1; i > 0; --i )
{
if ( QgsPanelWidget* panelWidget = qobject_cast<QgsPanelWidget*>( mStackedWidget->widget( i ) ) )
{
panelWidget->acceptPanel();
}
}
setUpdatesEnabled( true );
mStackedWidget->setUpdatesEnabled( true );
}

void QgsPanelWidgetStack::showPanel( QgsPanelWidget *panel )
{
mTitles.push( panel->panelTitle() );
Expand Down
17 changes: 17 additions & 0 deletions src/gui/qgspanelwidgetstack.h
Expand Up @@ -66,6 +66,8 @@ class GUI_EXPORT QgsPanelWidgetStack : public QWidget, private Ui::QgsRendererWi
* Removes the main panel widget from the stack and transfers ownsership to the
* caller.
* @return The main widget that is set in the stack.
* @note Calling this will clear out any current stacked panels by accepting
* each panel in turn.
* @see mainPanel()
* @see setMainPanel()
*/
Expand All @@ -77,14 +79,29 @@ class GUI_EXPORT QgsPanelWidgetStack : public QWidget, private Ui::QgsRendererWi
*/
void clear();

/**
* Returns the panel currently shown in the stack.
* @note added in QGIS 3.0
*/
QgsPanelWidget* currentPanel();

public slots:
/**
* Accept the current active widget in the stack.
*
* Calls the panelAccepeted signal on the active widget.
* @see acceptAllPanels()
*/
void acceptCurrentPanel();

/**
* Accepts all panel widgets open in the stack in turn until until only the mainPanel()
* remains.
* @see acceptCurrentPanel();
* @note added in QGIS 3.0
*/
void acceptAllPanels();

/**
* Show a panel in the stack widget. Will connect to the panels showPanel event to handle
* nested panels. Auto switches the the given panel for the user.
Expand Down
1 change: 1 addition & 0 deletions tests/src/python/CMakeLists.txt
Expand Up @@ -69,6 +69,7 @@ ADD_PYTHON_TEST(PyQgsPalLabelingCanvas test_qgspallabeling_canvas.py)
ADD_PYTHON_TEST(PyQgsPalLabelingComposer test_qgspallabeling_composer.py)
ADD_PYTHON_TEST(PyQgsPalLabelingPlacement test_qgspallabeling_placement.py)
ADD_PYTHON_TEST(PyQgsPanelWidget test_qgspanelwidget.py)
ADD_PYTHON_TEST(PyQgsPanelWidgetStack test_qgspanelwidgetstack.py)
ADD_PYTHON_TEST(PyQgsPoint test_qgspoint.py)
ADD_PYTHON_TEST(PyQgsPointClusterRenderer test_qgspointclusterrenderer.py)
ADD_PYTHON_TEST(PyQgsPointDisplacementRenderer test_qgspointdisplacementrenderer.py)
Expand Down
165 changes: 165 additions & 0 deletions tests/src/python/test_qgspanelwidgetstack.py
@@ -0,0 +1,165 @@
# -*- coding: utf-8 -*-
"""QGIS Unit tests for QgsPanelWidgetStack.
.. note:: This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
"""
__author__ = 'Nyall Dawson'
__date__ = '05/10/2016'
__copyright__ = 'Copyright 2016, The QGIS Project'
# This will get replaced with a git SHA1 when you do a git archive
__revision__ = '$Format:%H$'

import qgis # NOQA

from qgis.PyQt.QtWidgets import QWidget
from qgis.gui import QgsPanelWidget, QgsPanelWidgetStack
from qgis.testing import start_app, unittest
from qgis.PyQt.QtTest import QSignalSpy

start_app()


class TestQgsPanelWidgetStack(unittest.TestCase):

def testMainPanel(self):
""" test mainPanel methods """

s = QgsPanelWidgetStack()

# no main panel
self.assertFalse(s.mainPanel())
self.assertFalse(s.takeMainPanel())

# set main panel
p1 = QgsPanelWidget()
s.setMainPanel(p1)
self.assertEqual(s.mainPanel(), p1)

# takeMainPanel()
self.assertEqual(s.takeMainPanel(), p1)
self.assertFalse(s.mainPanel())
self.assertFalse(s.takeMainPanel())

def testAddingPanels(self):
""" test adding panels to stack """

s = QgsPanelWidgetStack()
mp = QgsPanelWidget()
s.setMainPanel(mp)

p1 = QgsPanelWidget()
s.showPanel(p1)
self.assertEqual(s.currentPanel(), p1)

p2 = QgsPanelWidget()
s.showPanel(p2)
self.assertEqual(s.currentPanel(), p2)

def testAcceptCurrentPanel(self):
""" test accepting current panel """

s = QgsPanelWidgetStack()
# call on empty stack
s.acceptCurrentPanel()

mp = QgsPanelWidget()
s.setMainPanel(mp)
# call on main panel - should be no effect
s.acceptCurrentPanel()
self.assertEqual(s.mainPanel(), mp)
self.assertEqual(s.currentPanel(), mp)

# add panels
p1 = QgsPanelWidget()
s.showPanel(p1)
p2 = QgsPanelWidget()
s.showPanel(p2)

# accept them
self.assertEqual(s.currentPanel(), p2)
p2_accept_spy = QSignalSpy(p2.panelAccepted)
s.acceptCurrentPanel()
self.assertEqual(s.currentPanel(), p1)
self.assertEqual(len(p2_accept_spy), 1)
p1_accept_spy = QSignalSpy(p1.panelAccepted)
s.acceptCurrentPanel()
self.assertEqual(s.currentPanel(), mp)
self.assertEqual(len(p1_accept_spy), 1)

def testAcceptAllPanel(self):
""" test accepting all panels """
s = QgsPanelWidgetStack()
# call on empty stack
s.acceptAllPanels()

mp = QgsPanelWidget()
s.setMainPanel(mp)
# call on main panel - should be no effect
s.acceptAllPanels()
self.assertEqual(s.mainPanel(), mp)
self.assertEqual(s.currentPanel(), mp)

# add panels
p1 = QgsPanelWidget()
s.showPanel(p1)
p1_accept_spy = QSignalSpy(p1.panelAccepted)
p2 = QgsPanelWidget()
s.showPanel(p2)
p2_accept_spy = QSignalSpy(p2.panelAccepted)
p3 = QgsPanelWidget()
s.showPanel(p3)
p3_accept_spy = QSignalSpy(p3.panelAccepted)

# accept all
s.acceptAllPanels()
self.assertEqual(s.currentPanel(), mp)
self.assertEqual(len(p1_accept_spy), 1)
self.assertEqual(len(p2_accept_spy), 1)
self.assertEqual(len(p3_accept_spy), 1)

def testClear(self):
""" test clearing stack """
s = QgsPanelWidgetStack()
# call on empty stack
s.clear()

# add panels
mp = QgsPanelWidget()
s.setMainPanel(mp)
p1 = QgsPanelWidget()
s.showPanel(p1)
p2 = QgsPanelWidget()
s.showPanel(p2)
p3 = QgsPanelWidget()
s.showPanel(p3)

# clear
s.clear()
self.assertFalse(s.currentPanel())
self.assertFalse(s.mainPanel())

def testTakeMainAcceptsAll(self):
""" test that taking the main panel accepts all open child panels"""
s = QgsPanelWidgetStack()
mp = QgsPanelWidget()
s.setMainPanel(mp)
p1 = QgsPanelWidget()
s.showPanel(p1)
p1_accept_spy = QSignalSpy(p1.panelAccepted)
p2 = QgsPanelWidget()
s.showPanel(p2)
p2_accept_spy = QSignalSpy(p2.panelAccepted)
p3 = QgsPanelWidget()
s.showPanel(p3)
p3_accept_spy = QSignalSpy(p3.panelAccepted)

# take main
s.takeMainPanel()
self.assertEqual(len(p1_accept_spy), 1)
self.assertEqual(len(p2_accept_spy), 1)
self.assertEqual(len(p3_accept_spy), 1)
if __name__ == '__main__':
unittest.main()

0 comments on commit 37e3dd7

Please sign in to comment.