From a211c982cfe6c92d4ba9e5fb79a3124c7f3e4a19 Mon Sep 17 00:00:00 2001 From: Sandro Santilli Date: Thu, 9 Jun 2016 19:00:08 +0200 Subject: [PATCH] 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. --- python/core/composer/qgscomposition.sip | 2 + .../composer/qgsgroupungroupitemscommand.sip | 43 +++ python/core/core.sip | 1 + src/core/CMakeLists.txt | 2 + src/core/composer/qgsaddremoveitemcommand.cpp | 4 + src/core/composer/qgscomposeritemgroup.cpp | 2 +- src/core/composer/qgscomposition.cpp | 66 ++++- src/core/composer/qgscomposition.h | 2 + .../composer/qgsgroupungroupitemscommand.cpp | 96 +++++++ .../composer/qgsgroupungroupitemscommand.h | 75 +++++ tests/src/core/testqgscomposergroup.cpp | 267 +++++++++++++++++- 11 files changed, 532 insertions(+), 28 deletions(-) create mode 100644 python/core/composer/qgsgroupungroupitemscommand.sip create mode 100644 src/core/composer/qgsgroupungroupitemscommand.cpp create mode 100644 src/core/composer/qgsgroupungroupitemscommand.h diff --git a/python/core/composer/qgscomposition.sip b/python/core/composer/qgscomposition.sip index 4f721d41cea..46fdb1c665a 100644 --- a/python/core/composer/qgscomposition.sip +++ b/python/core/composer/qgscomposition.sip @@ -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*/ diff --git a/python/core/composer/qgsgroupungroupitemscommand.sip b/python/core/composer/qgsgroupungroupitemscommand.sip new file mode 100644 index 00000000000..756635cccad --- /dev/null +++ b/python/core/composer/qgsgroupungroupitemscommand.sip @@ -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 ); + +}; + + diff --git a/python/core/core.sip b/python/core/core.sip index 8cd28fbac27..b5351fab361 100644 --- a/python/core/core.sip +++ b/python/core/core.sip @@ -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 diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index adc004c2309..00c9ca7852d 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -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 diff --git a/src/core/composer/qgsaddremoveitemcommand.cpp b/src/core/composer/qgsaddremoveitemcommand.cpp index 9ddb02abb7b..aee29dc9b9c 100644 --- a/src/core/composer/qgsaddremoveitemcommand.cpp +++ b/src/core/composer/qgsaddremoveitemcommand.cpp @@ -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 ); diff --git a/src/core/composer/qgscomposeritemgroup.cpp b/src/core/composer/qgscomposeritemgroup.cpp index b419ecffa91..cadf1f3abcf 100644 --- a/src/core/composer/qgscomposeritemgroup.cpp +++ b/src/core/composer/qgscomposeritemgroup.cpp @@ -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 diff --git a/src/core/composer/qgscomposition.cpp b/src/core/composer/qgscomposition.cpp index bbd3cc61245..545fe5c07e6 100644 --- a/src/core/composer/qgscomposition.cpp +++ b/src/core/composer/qgscomposition.cpp @@ -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 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::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 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 groupedItems = group->items(); QSet::iterator itemIt = groupedItems.begin(); for ( ; itemIt != groupedItems.end(); ++itemIt ) @@ -2041,10 +2071,9 @@ QList 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( 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( 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 groupedItems = itemGroup->items(); + QgsDebugMsg( QString( "itemGroup contains %1 items" ) .arg( groupedItems.size() ) ); QSet::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( 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( item ); + if ( group ) + { + emit composerItemGroupAdded( group ); + } } void QgsComposition::updatePaperItems() diff --git a/src/core/composer/qgscomposition.h b/src/core/composer/qgscomposition.h index 6b10232cdd5..b1146171acc 100644 --- a/src/core/composer/qgscomposition.h +++ b/src/core/composer/qgscomposition.h @@ -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*/ diff --git a/src/core/composer/qgsgroupungroupitemscommand.cpp b/src/core/composer/qgsgroupungroupitemscommand.cpp new file mode 100644 index 00000000000..0ee18d85d92 --- /dev/null +++ b/src/core/composer/qgsgroupungroupitemscommand.cpp @@ -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::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 ); +} diff --git a/src/core/composer/qgsgroupungroupitemscommand.h b/src/core/composer/qgsgroupungroupitemscommand.h new file mode 100644 index 00000000000..0c2b5488a59 --- /dev/null +++ b/src/core/composer/qgsgroupungroupitemscommand.h @@ -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 +#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 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 diff --git a/tests/src/core/testqgscomposergroup.cpp b/tests/src/core/testqgscomposergroup.cpp index 785071bb23a..3b2468ed20a 100644 --- a/tests/src/core/testqgscomposergroup.cpp +++ b/tests/src/core/testqgscomposergroup.cpp @@ -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 +#include #include 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(); + QSignalSpy spyPolygonAdded( mComposition, SIGNAL( composerPolygonAdded( QgsComposerPolygon* ) ) ); + QCOMPARE( spyPolygonAdded.count(), 0 ); + + qRegisterMetaType(); + QSignalSpy spyGroupAdded( mComposition, SIGNAL( composerItemGroupAdded( QgsComposerItemGroup* ) ) ); + QCOMPARE( spyGroupAdded.count(), 0 ); + + qRegisterMetaType(); + 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 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 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 )