Allow to undo/redo composer grouping/ungrouping

Fixes #11371 (crash on ungrouping after moving the group) and more
undo/redo related issues.

Enable pending test for the crash (now passing) and add many more
undo/redo related ones (including signals testing).

Includes a new QgsGroupUngroupItemsCommand class
and its SIP bindings.
This commit is contained in:
Sandro Santilli 2016-06-09 19:00:08 +02:00
parent efdbb87406
commit a211c982cf
11 changed files with 532 additions and 28 deletions

View File

@ -874,6 +874,8 @@ class QgsComposition : QGraphicsScene
void composerPolylineAdded( QgsComposerPolyline* polyline );
/** Is emitted when a new composer html has been added to the view*/
void composerHtmlFrameAdded( QgsComposerHtml* html, QgsComposerFrame* frame );
/** Is emitted when a new item group has been added to the view*/
void composerItemGroupAdded( QgsComposerItemGroup* group );
/** Is emitted when new composer label has been added to the view*/
void composerLabelAdded( QgsComposerLabel* label );
/** Is emitted when new composer map has been added to the view*/

View File

@ -0,0 +1,43 @@
/** A composer command class for grouping / ungrouping composer items.
*
* If mState == Ungrouped, the command owns the group item
*/
class QgsGroupUngroupItemsCommand: QObject, QUndoCommand
{
%TypeHeaderCode
#include "qgsgroupungroupitemscommand.h"
%End
public:
/** Command kind, and state */
enum State
{
Grouped = 0,
Ungrouped
};
/** Create a group or ungroup command
*
* @param s command kind (@see State)
* @param item the group item being created or ungrouped
* @param c the composition including this group
* @param text command label
* @param parent parent command, if any
*
*/
QgsGroupUngroupItemsCommand( State s, QgsComposerItemGroup* item, QgsComposition* c, const QString& text, QUndoCommand* parent = nullptr );
~QgsGroupUngroupItemsCommand();
void redo();
void undo();
signals:
/** Signals addition of an item (the group) */
void itemAdded( QgsComposerItem* item );
/** Signals removal of an item (the group) */
void itemRemoved( QgsComposerItem* item );
};

View File

@ -168,6 +168,7 @@
%Include auth/qgsauthmethod.sip
%Include composer/qgsaddremoveitemcommand.sip
%Include composer/qgsgroupungroupitemscommand.sip
%Include composer/qgsaddremovemultiframecommand.sip
%Include composer/qgsatlascomposition.sip
%Include composer/qgscomposerarrow.sip

View File

@ -268,6 +268,7 @@ SET(QGIS_CORE_SRCS
composer/qgscomposerutils.cpp
composer/qgscomposition.cpp
composer/qgsdoubleboxscalebarstyle.cpp
composer/qgsgroupungroupitemscommand.cpp
composer/qgslegendmodel.cpp
composer/qgsnumericscalebarstyle.cpp
composer/qgspaperitem.cpp
@ -535,6 +536,7 @@ SET(QGIS_CORE_MOC_HDRS
composer/qgscomposertablev2.h
composer/qgscomposertexttable.h
composer/qgscomposition.h
composer/qgsgroupungroupitemscommand.h
composer/qgslegendmodel.h
composer/qgspaperitem.h

View File

@ -36,6 +36,7 @@ QgsAddRemoveItemCommand::~QgsAddRemoveItemCommand()
void QgsAddRemoveItemCommand::redo()
{
QUndoCommand::redo(); // call redo() on all childs
if ( mFirstRun )
{
mFirstRun = false;
@ -46,6 +47,7 @@ void QgsAddRemoveItemCommand::redo()
void QgsAddRemoveItemCommand::undo()
{
QUndoCommand::undo(); // call undo() on all childs, in reverse order
if ( mFirstRun )
{
mFirstRun = false;
@ -58,6 +60,7 @@ void QgsAddRemoveItemCommand::switchState()
{
if ( mState == Added )
{
// Remove
if ( mComposition )
{
mComposition->itemsModel()->setItemRemoved( mItem );
@ -68,6 +71,7 @@ void QgsAddRemoveItemCommand::switchState()
}
else //Removed
{
// Add
if ( mComposition )
{
mComposition->itemsModel()->setItemRestored( mItem );

View File

@ -36,7 +36,7 @@ QgsComposerItemGroup::~QgsComposerItemGroup()
//loop through group members and remove them from the scene
Q_FOREACH ( QgsComposerItem* item, mItems )
{
if ( !item )
if ( !item || item->isRemoved() )
continue;
//inform model that we are about to remove an item from the scene

View File

@ -35,6 +35,7 @@
#include "qgscomposerattributetablev2.h"
#include "qgsaddremovemultiframecommand.h"
#include "qgscomposermultiframecommand.h"
#include "qgsgroupungroupitemscommand.h"
#include "qgspaintenginehack.h"
#include "qgspaperitem.h"
#include "qgsproject.h"
@ -1589,6 +1590,10 @@ void QgsComposition::addItemsFromXML( const QDomElement& elem, const QDomDocumen
QgsComposerItemGroup *newGroup = new QgsComposerItemGroup( this );
newGroup->readXML( groupElem, doc );
addItem( newGroup );
if ( addUndoCommands )
{
pushAddRemoveCommand( newGroup, tr( "Group added" ) );
}
}
//Since this function adds items grouped by type, and each item is added to end of
@ -2014,14 +2019,28 @@ QgsComposerItemGroup *QgsComposition::groupItems( QList<QgsComposerItem *> items
}
QgsComposerItemGroup* itemGroup = new QgsComposerItemGroup( this );
QgsDebugMsg( QString( "itemgroup created with %1 items (%2 to be added)" ) .arg( itemGroup->items().size() ).arg( items.size() ) );
QList<QgsComposerItem*>::iterator itemIter = items.begin();
for ( ; itemIter != items.end(); ++itemIter )
{
itemGroup->addItem( *itemIter );
QgsDebugMsg( QString( "itemgroup now has %1" )
.arg( itemGroup->items().size() ) );
}
addItem( itemGroup );
QgsGroupUngroupItemsCommand* c = new QgsGroupUngroupItemsCommand( QgsGroupUngroupItemsCommand::Grouped, itemGroup, this, tr( "Items grouped" ) );
QObject::connect( c, SIGNAL( itemRemoved( QgsComposerItem* ) ), this, SIGNAL( itemRemoved( QgsComposerItem* ) ) );
QObject::connect( c, SIGNAL( itemAdded( QgsComposerItem* ) ), this, SLOT( sendItemAddedSignal( QgsComposerItem* ) ) );
undoStack()->push( c );
QgsProject::instance()->setDirty( true );
//QgsDebugMsg( QString( "itemgroup after pushAddRemove has %1" ) .arg( itemGroup->items().size() ) );
emit composerItemGroupAdded( itemGroup );
return itemGroup;
}
@ -2033,6 +2052,17 @@ QList<QgsComposerItem *> QgsComposition::ungroupItems( QgsComposerItemGroup* gro
return ungroupedItems;
}
// group ownership transferred to QgsGroupUngroupItemsCommand
// Call this before removing group items so it can keep note
// of contents
QgsGroupUngroupItemsCommand* c = new QgsGroupUngroupItemsCommand( QgsGroupUngroupItemsCommand::Ungrouped, group, this, tr( "Items ungrouped" ) );
QObject::connect( c, SIGNAL( itemRemoved( QgsComposerItem* ) ), this, SIGNAL( itemRemoved( QgsComposerItem* ) ) );
QObject::connect( c, SIGNAL( itemAdded( QgsComposerItem* ) ), this, SLOT( sendItemAddedSignal( QgsComposerItem* ) ) );
undoStack()->push( c );
QgsProject::instance()->setDirty( true );
QSet<QgsComposerItem*> groupedItems = group->items();
QSet<QgsComposerItem*>::iterator itemIt = groupedItems.begin();
for ( ; itemIt != groupedItems.end(); ++itemIt )
@ -2041,10 +2071,9 @@ QList<QgsComposerItem *> QgsComposition::ungroupItems( QgsComposerItemGroup* gro
}
group->removeItems();
removeComposerItem( group, false, false );
emit itemRemoved( group );
delete( group );
// note: emits itemRemoved
removeComposerItem( group, false, false );
return ungroupedItems;
}
@ -2636,6 +2665,7 @@ void QgsComposition::addComposerTableFrame( QgsComposerAttributeTableV2 *table,
emit composerTableFrameAdded( table, frame );
}
/* public */
void QgsComposition::removeComposerItem( QgsComposerItem* item, const bool createCommand, const bool removeGroupItems )
{
QgsComposerMap* map = dynamic_cast<QgsComposerMap *>( item );
@ -2644,25 +2674,36 @@ void QgsComposition::removeComposerItem( QgsComposerItem* item, const bool creat
{
mItemsModel->setItemRemoved( item );
removeItem( item );
emit itemRemoved( item );
QgsDebugMsg( QString( "removeComposerItem called, createCommand:%1 removeGroupItems:%2" )
.arg( createCommand ).arg( removeGroupItems ) );
QgsComposerItemGroup* itemGroup = dynamic_cast<QgsComposerItemGroup*>( item );
if ( itemGroup && removeGroupItems )
{
//add add/remove item command for every item in the group
QUndoCommand* parentCommand = new QUndoCommand( tr( "Remove item group" ) );
QgsDebugMsg( QString( "itemGroup && removeGroupItems" ) );
// Takes ownership of itemGroup
QgsAddRemoveItemCommand* parentCommand = new QgsAddRemoveItemCommand(
QgsAddRemoveItemCommand::Removed, itemGroup, this,
tr( "Remove item group" ) );
connectAddRemoveCommandSignals( parentCommand );
//add add/remove item command for every item in the group
QSet<QgsComposerItem*> groupedItems = itemGroup->items();
QgsDebugMsg( QString( "itemGroup contains %1 items" ) .arg( groupedItems.size() ) );
QSet<QgsComposerItem*>::iterator it = groupedItems.begin();
for ( ; it != groupedItems.end(); ++it )
{
mItemsModel->setItemRemoved( *it );
removeItem( *it );
QgsAddRemoveItemCommand* subcommand = new QgsAddRemoveItemCommand( QgsAddRemoveItemCommand::Removed, *it, this, "", parentCommand );
connectAddRemoveCommandSignals( subcommand );
emit itemRemoved( *it );
}
undoStack()->push( parentCommand );
emit itemRemoved( itemGroup );
delete itemGroup;
}
else
{
@ -2674,19 +2715,13 @@ void QgsComposition::removeComposerItem( QgsComposerItem* item, const bool creat
{
multiFrame = static_cast<QgsComposerFrame*>( item )->multiFrame();
item->beginItemCommand( tr( "Frame deleted" ) );
emit itemRemoved( item );
item->endItemCommand();
}
else
{
emit itemRemoved( item );
pushAddRemoveCommand( item, tr( "Item deleted" ), QgsAddRemoveItemCommand::Removed );
}
}
else
{
emit itemRemoved( item );
}
//check if there are frames left. If not, remove the multi frame
if ( frameItem && multiFrame )
@ -2823,6 +2858,11 @@ void QgsComposition::sendItemAddedSignal( QgsComposerItem* item )
emit selectedItemChanged( frame );
return;
}
QgsComposerItemGroup* group = dynamic_cast<QgsComposerItemGroup*>( item );
if ( group )
{
emit composerItemGroupAdded( group );
}
}
void QgsComposition::updatePaperItems()

View File

@ -1119,6 +1119,8 @@ class CORE_EXPORT QgsComposition : public QGraphicsScene
void composerPolylineAdded( QgsComposerPolyline* polyline );
/** Is emitted when a new composer html has been added to the view*/
void composerHtmlFrameAdded( QgsComposerHtml* html, QgsComposerFrame* frame );
/** Is emitted when a new item group has been added to the view*/
void composerItemGroupAdded( QgsComposerItemGroup* group );
/** Is emitted when new composer label has been added to the view*/
void composerLabelAdded( QgsComposerLabel* label );
/** Is emitted when new composer map has been added to the view*/

View File

@ -0,0 +1,96 @@
/***************************************************************************
qgsgroupungroupitemscommand.cpp
---------------------------
begin : 2016-06-09
copyright : (C) 2016 by Sandro Santilli
email : strk at kbt dot io
***************************************************************************/
/***************************************************************************
* *
* 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. *
* *
***************************************************************************/
#include "qgsgroupungroupitemscommand.h"
#include "qgscomposeritem.h"
#include "qgscomposeritemgroup.h"
#include "qgscomposition.h"
#include "qgsproject.h"
#include "qgscomposermodel.h"
#include "qgslogger.h"
QgsGroupUngroupItemsCommand::QgsGroupUngroupItemsCommand( State s, QgsComposerItemGroup* item, QgsComposition* c, const QString& text, QUndoCommand* parent ):
QUndoCommand( text, parent ), mGroup( item ), mComposition( c ), mState( s ), mFirstRun( true )
{
mItems = mGroup->items();
}
QgsGroupUngroupItemsCommand::~QgsGroupUngroupItemsCommand()
{
if ( mState == Ungrouped )
{
//command class stores the item if ungrouped from the composition
delete mGroup;
}
}
void QgsGroupUngroupItemsCommand::redo()
{
if ( mFirstRun )
{
mFirstRun = false;
return;
}
switchState();
}
void QgsGroupUngroupItemsCommand::undo()
{
if ( mFirstRun )
{
mFirstRun = false;
return;
}
switchState();
}
void QgsGroupUngroupItemsCommand::switchState()
{
if ( mState == Grouped )
{
// ungroup
if ( mComposition )
{
// This is probably redundant
mComposition->itemsModel()->setItemRemoved( mGroup );
mComposition->removeItem( mGroup );
}
mGroup->removeItems();
emit itemRemoved( mGroup );
mState = Ungrouped;
}
else //Ungrouped
{
// group
if ( mComposition )
{
//delete mGroup; mGroup = new QgsComposerItemGroup( mCompoiser );
QSet<QgsComposerItem*>::iterator itemIter = mItems.begin();
for ( ; itemIter != mItems.end(); ++itemIter )
{
mGroup->addItem( *itemIter );
QgsDebugMsg( QString( "itemgroup now has %1" ) .arg( mGroup->items().size() ) );
}
// Add the group
mComposition->itemsModel()->setItemRestored( mGroup );
mComposition->addItem( mGroup );
}
mState = Grouped;
emit itemAdded( mGroup );
}
QgsProject::instance()->setDirty( true );
}

View File

@ -0,0 +1,75 @@
/***************************************************************************
qgsgroupungroupitemscommand.h
------------------------
begin : 2016-06-09
copyright : (C) 2016 by Sandro Santilli
email : strk at kbt dot io
***************************************************************************/
/***************************************************************************
* *
* 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. *
* *
***************************************************************************/
#ifndef QGSGROUPUNGROUPITEMSCOMMAND_H
#define QGSGROUPUNGROUPITEMSCOMMAND_H
#include <QUndoCommand>
#include "qgscomposeritemgroup.h"
class QgsComposerItem;
class QgsComposition;
/** A composer command class for grouping / ungrouping composer items.
*
* If mState == Ungrouped, the command owns the group item
*/
class CORE_EXPORT QgsGroupUngroupItemsCommand: public QObject, public QUndoCommand
{
Q_OBJECT
public:
/** Command kind, and state */
enum State
{
Grouped = 0,
Ungrouped
};
/** Create a group or ungroup command
*
* @param s command kind (@see State)
* @param item the group item being created or ungrouped
* @param c the composition including this group
* @param text command label
* @param parent parent command, if any
*
*/
QgsGroupUngroupItemsCommand( State s, QgsComposerItemGroup* item, QgsComposition* c, const QString& text, QUndoCommand* parent = nullptr );
~QgsGroupUngroupItemsCommand();
void redo() override;
void undo() override;
signals:
/** Signals addition of an item (the group) */
void itemAdded( QgsComposerItem* item );
/** Signals removal of an item (the group) */
void itemRemoved( QgsComposerItem* item );
private:
QgsComposerItemGroup* mGroup;
QSet<QgsComposerItem*> mItems;
QgsComposition* mComposition;
State mState;
bool mFirstRun; //flag to prevent execution when the command is pushed to the QUndoStack
//changes between added / removed state
void switchState();
};
#endif // QGSGROUPUNGROUPITEMSCOMMAND_H

View File

@ -17,11 +17,14 @@
#include "qgscomposeritemgroup.h"
#include "qgscomposerlabel.h"
#include "qgscomposerpolygon.h"
#include "qgscomposition.h"
#include "qgsmultirenderchecker.h"
#include "qgsapplication.h"
#include "qgslogger.h"
#include <QObject>
#include <QtTest/QSignalSpy>
#include <QtTest/QtTest>
class TestQgsComposerGroup : public QObject
@ -48,6 +51,9 @@ class TestQgsComposerGroup : public QObject
void undoRedo(); //test that group/ungroup undo/redo commands don't crash
private:
void dumpUndoStack( const QUndoStack&, QString prefix = "" ) const;
QgsComposition *mComposition;
QgsMapSettings *mMapSettings;
QgsComposerLabel* mItem1;
@ -56,6 +62,18 @@ class TestQgsComposerGroup : public QObject
QString mReport;
};
// private
void TestQgsComposerGroup::dumpUndoStack( const QUndoStack& us, QString prefix ) const
{
if ( ! prefix.isEmpty() ) prefix += ": ";
for ( int i = 0; i < us.count(); ++i )
{
QgsDebugMsg( QString( "%4US %1: %2%3" )
.arg( i ). arg( i >= us.index() ? "-" : "" )
.arg( us.text( i ) ) .arg( prefix ) );
}
}
void TestQgsComposerGroup::initTestCase()
{
QgsApplication::init();
@ -161,36 +179,257 @@ void TestQgsComposerGroup::deleteGroup()
QCOMPARE( items.size(), 1 );
QVERIFY( mItem1->isRemoved() );
QVERIFY( mItem2->isRemoved() );
QVERIFY( mGroup->isRemoved() );
}
Q_DECLARE_METATYPE( QgsComposerItemGroup * );
Q_DECLARE_METATYPE( QgsComposerPolygon * );
Q_DECLARE_METATYPE( QgsComposerItem * );
void TestQgsComposerGroup::undoRedo()
{
#if 0 //expected fail - see #11371
QgsComposerPolygon *item1, *item2;
int polygonsAdded = 0;
int groupsAdded = 0;
int itemsRemoved = 0;
qRegisterMetaType<QgsComposerPolygon *>();
QSignalSpy spyPolygonAdded( mComposition, SIGNAL( composerPolygonAdded( QgsComposerPolygon* ) ) );
QCOMPARE( spyPolygonAdded.count(), 0 );
qRegisterMetaType<QgsComposerItemGroup *>();
QSignalSpy spyGroupAdded( mComposition, SIGNAL( composerItemGroupAdded( QgsComposerItemGroup* ) ) );
QCOMPARE( spyGroupAdded.count(), 0 );
qRegisterMetaType<QgsComposerItem *>();
QSignalSpy spyItemRemoved( mComposition, SIGNAL( itemRemoved( QgsComposerItem* ) ) );
QCOMPARE( spyItemRemoved.count(), 0 );
//test for crash when undo/redoing with groups
// Set initial condition
QUndoStack *us = mComposition->undoStack();
QgsDebugMsg( QString( "clearing" ) );
us->clear();
QgsDebugMsg( QString( "clearing completed" ) );
QList<QgsComposerItem*> items;
mComposition->composerItems( items );
QCOMPARE( items.size(), 1 ); // paper only
QgsDebugMsg( QString( "clear stack count:%1 index:%2" ) .arg( us->count() ) .arg( us->index() ) );
//create some items
mItem1 = new QgsComposerLabel( mComposition );
mComposition->addItem( mItem1 );
mItem2 = new QgsComposerLabel( mComposition );
mComposition->addItem( mItem2 );
item1 = new QgsComposerPolygon( QPolygonF( QRectF( 0, 0, 1, 1 ) ), mComposition ); //QgsComposerLabel( mComposition );
mComposition->addComposerPolygon( item1 );
QCOMPARE( spyPolygonAdded.count(), ++polygonsAdded );
item2 = new QgsComposerPolygon( QPolygonF( QRectF( -1, -2, 1, 1 ) ), mComposition ); //QgsComposerLabel( mComposition );
mComposition->addComposerPolygon( item2 );
QCOMPARE( spyPolygonAdded.count(), ++polygonsAdded );
mComposition->composerItems( items );
QCOMPARE( items.size(), 3 ); // paper, 2 shapes
QgsDebugMsg( QString( "addedItems stack count:%1 index:%2" ) .arg( us->count() ) .arg( us->index() ) );
QCOMPARE( item1->pos(), QPointF( 0, 0 ) );
QCOMPARE( item2->pos(), QPointF( -1, -2 ) );
//dumpUndoStack(*us, "after initial items addition");
//group items
QList<QgsComposerItem*> items;
items << mItem1 << mItem2;
items.clear();
items << item1 << item2;
mGroup = mComposition->groupItems( items );
QCOMPARE( spyPolygonAdded.count(), polygonsAdded );
QCOMPARE( spyGroupAdded.count(), ++groupsAdded );
QCOMPARE( spyItemRemoved.count(), itemsRemoved );
QCOMPARE( mGroup->items().size(), 2 );
mComposition->composerItems( items );
QCOMPARE( items.size(), 4 ); // paper, 2 shapes, 1 group
QVERIFY( ! item1->isRemoved() );
QCOMPARE( item1->pos(), QPointF( 0, 0 ) );
QVERIFY( ! item2->isRemoved() );
QCOMPARE( item2->pos(), QPointF( -1, -2 ) );
QVERIFY( ! mGroup->isRemoved() );
QCOMPARE( mGroup->pos(), QPointF( -1, -2 ) );
//dumpUndoStack(*us, "after initial items addition");
//move, and ungroup
mGroup->beginCommand( "move" );
//move group
QgsDebugMsg( QString( "moving group" ) );
mGroup->beginCommand( "move group" );
mGroup->move( 10.0, 20.0 );
mGroup->endCommand();
QCOMPARE( spyPolygonAdded.count(), polygonsAdded );
QCOMPARE( spyGroupAdded.count(), groupsAdded );
QCOMPARE( spyItemRemoved.count(), itemsRemoved );
QgsDebugMsg( QString( "groupItems stack count:%1 index:%2" ) .arg( us->count() ) .arg( us->index() ) );
QCOMPARE( mGroup->items().size(), 2 );
mComposition->composerItems( items );
QCOMPARE( items.size(), 4 ); // paper, 2 shapes, 1 group
QVERIFY( ! item1->isRemoved() );
QCOMPARE( item1->pos(), QPointF( 10, 20 ) );
QVERIFY( ! item2->isRemoved() );
QCOMPARE( item2->pos(), QPointF( 9, 18 ) );
QVERIFY( ! mGroup->isRemoved() );
QCOMPARE( mGroup->pos(), QPointF( 9, 18 ) );
//ungroup
QgsDebugMsg( QString( "ungrouping" ) );
mComposition->ungroupItems( mGroup );
QCOMPARE( spyPolygonAdded.count(), polygonsAdded );
QCOMPARE( spyGroupAdded.count(), groupsAdded );
QCOMPARE( spyItemRemoved.count(), ++itemsRemoved );
mComposition->composerItems( items );
QCOMPARE( items.size(), 3 ); // paper, 2 shapes
QVERIFY( ! item1->isRemoved() );
QVERIFY( ! item2->isRemoved() );
QVERIFY( mGroup->isRemoved() );
QCOMPARE( mGroup->pos(), QPointF( 9, 18 ) ); // should not rely on this
//dumpUndoStack(*us, "after ungroup");
// US 0: Items grouped
// US 1: move group
// US 2: Remove item group
//undo
mComposition->undoStack()->undo();
//undo (groups again) -- crashed here before #11371 got fixed
QgsDebugMsg( QString( "undo ungrouping" ) );
us->undo();
QCOMPARE( spyPolygonAdded.count(), polygonsAdded );
QCOMPARE( spyGroupAdded.count(), ++groupsAdded );
QCOMPARE( spyItemRemoved.count(), itemsRemoved );
QCOMPARE( mGroup->items().size(), 2 ); // WARNING: might not be alive anymore
mComposition->composerItems( items );
QCOMPARE( items.size(), 4 ); // paper, 2 shapes, 1 group
QVERIFY( ! item1->isRemoved() );
QCOMPARE( item1->pos(), QPointF( 10, 20 ) );
QVERIFY( ! item2->isRemoved() );
QCOMPARE( item2->pos(), QPointF( 9, 18 ) );
QVERIFY( ! mGroup->isRemoved() );
QCOMPARE( mGroup->pos(), QPointF( 9, 18 ) );
//dumpUndoStack(*us, "after undo ungroup");
// US 0: Items grouped
// US 1: move group
// US 2: -Remove item group
//redo
mComposition->undoStack()->redo();
#endif
//remove group
QgsDebugMsg( QString( "remove group" ) );
mComposition->removeComposerItem( mGroup, true, true );
QCOMPARE( spyPolygonAdded.count(), polygonsAdded );
QCOMPARE( spyGroupAdded.count(), groupsAdded );
itemsRemoved += 3; // the group and the two items
QCOMPARE( spyItemRemoved.count(), itemsRemoved );
mComposition->composerItems( items );
QCOMPARE( items.size(), 1 ); // paper only
QgsDebugMsg( QString( "remove stack count:%1 index:%2" ) .arg( us->count() ) .arg( us->index() ) );
//dumpUndoStack(*us, "after remove group");
// US 0: Items grouped
// US 1: move group
// US 2: Remove item group
//undo remove group
QgsDebugMsg( QString( "undo remove group" ) );
us->undo();
polygonsAdded += 2;
QCOMPARE( spyPolygonAdded.count(), polygonsAdded );
QCOMPARE( spyGroupAdded.count(), ++groupsAdded );
QCOMPARE( spyItemRemoved.count(), itemsRemoved );
mComposition->composerItems( items );
QCOMPARE( mGroup->items().size(), 2 );
QCOMPARE( items.size(), 4 ); // paper, 2 shapes, 1 group
QgsDebugMsg( QString( "undo stack count:%1 index:%2" ) .arg( us->count() ) .arg( us->index() ) );
//dumpUndoStack(*us, "after undo remove group");
// US 0: Items grouped
// US 1: move group
// US 2: -Remove item group
//undo move group
QgsDebugMsg( QString( "undo move group" ) );
us->undo();
QCOMPARE( spyPolygonAdded.count(), polygonsAdded );
QCOMPARE( spyGroupAdded.count(), groupsAdded );
QCOMPARE( spyItemRemoved.count(), itemsRemoved );
QCOMPARE( mGroup->items().size(), 2 );
mComposition->composerItems( items );
QCOMPARE( items.size(), 4 ); // paper, 2 shapes, 1 group
QCOMPARE( item1->isGroupMember(), true );
QCOMPARE( item2->isGroupMember(), true );
QVERIFY( ! item1->isRemoved() );
QCOMPARE( item1->pos(), QPointF( 0, 0 ) );
QVERIFY( ! item2->isRemoved() );
QCOMPARE( item2->pos(), QPointF( -1, -2 ) );
QVERIFY( ! mGroup->isRemoved() );
QCOMPARE( mGroup->pos(), QPointF( -1, -2 ) );
//dumpUndoStack(*us, "after undo move group");
// US 0: Items grouped
// US 1: -move group
// US 2: -Remove item group
//undo group
QgsDebugMsg( QString( "undo group" ) );
us->undo();
QCOMPARE( spyPolygonAdded.count(), polygonsAdded );
QCOMPARE( spyGroupAdded.count(), groupsAdded );
QCOMPARE( spyItemRemoved.count(), ++itemsRemoved );
//QCOMPARE( mGroup->items().size(), 2 ); // not important
mComposition->composerItems( items );
QCOMPARE( items.size(), 3 ); // paper, 2 shapes
QCOMPARE( item1->isGroupMember(), false );
QCOMPARE( item2->isGroupMember(), false );
QVERIFY( ! item1->isRemoved() );
QCOMPARE( item1->pos(), QPointF( 0, 0 ) );
QVERIFY( ! item2->isRemoved() );
QCOMPARE( item2->pos(), QPointF( -1, -2 ) );
QVERIFY( mGroup->isRemoved() );
//QCOMPARE( mGroup->pos(), QPointF( -1, -2 ) );
//dumpUndoStack(*us, "after undo group");
// US 0: -Items grouped
// US 1: -move group
// US 2: -Remove item group
//redo group
QgsDebugMsg( QString( "redo group" ) );
us->redo();
QCOMPARE( spyPolygonAdded.count(), polygonsAdded );
QCOMPARE( spyGroupAdded.count(), ++groupsAdded );
QCOMPARE( spyItemRemoved.count(), itemsRemoved );
mComposition->composerItems( items );
QCOMPARE( items.size(), 4 ); // paper, 2 shapes, 1 group
QCOMPARE( item1->isGroupMember(), true );
QCOMPARE( item2->isGroupMember(), true );
//// QCOMPARE( mGroup->pos(), QPointF( 0, 0 ) ); // getting nan,nan here
//dumpUndoStack(*us, "after redo group");
// US 0: Items grouped
// US 1: -move group
// US 2: -Remove item group
//redo move group
QgsDebugMsg( QString( "redo move group" ) );
us->redo();
QCOMPARE( spyPolygonAdded.count(), polygonsAdded );
QCOMPARE( spyGroupAdded.count(), groupsAdded );
QCOMPARE( spyItemRemoved.count(), itemsRemoved );
mComposition->composerItems( items );
QCOMPARE( items.size(), 4 ); // paper, 2 shapes, 1 group
QCOMPARE( item1->isGroupMember(), true );
QCOMPARE( item2->isGroupMember(), true );
QCOMPARE( mGroup->pos(), QPointF( 9, 18 ) );
//dumpUndoStack(*us, "after redo move group");
// US 0: Items grouped
// US 1: move group
// US 2: -Remove item group
//redo remove group
QgsDebugMsg( QString( "redo remove group" ) );
us->redo();
QCOMPARE( spyPolygonAdded.count(), polygonsAdded );
QCOMPARE( spyGroupAdded.count(), groupsAdded );
itemsRemoved += 3; // 1 group, 2 contained items
QCOMPARE( spyItemRemoved.count(), itemsRemoved );
mComposition->composerItems( items );
QCOMPARE( items.size(), 1 ); // paper only
QgsDebugMsg( QString( "undo stack count:%1 index:%2" ) .arg( us->count() ) .arg( us->index() ) );
//dumpUndoStack(*us, "after redo remove group");
// US 0: Items grouped
// US 1: move group
// US 2: Remove item group
//unwind the whole stack
us->clear();
QgsDebugMsg( QString( "clear stack count:%1 index:%2" ) .arg( us->count() ) .arg( us->index() ) );
}
QTEST_MAIN( TestQgsComposerGroup )