Skip to content

Commit a211c98

Browse files
committedJun 17, 2016
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.
1 parent efdbb87 commit a211c98

11 files changed

+533
-29
lines changed
 

‎python/core/composer/qgscomposition.sip

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -874,6 +874,8 @@ class QgsComposition : QGraphicsScene
874874
void composerPolylineAdded( QgsComposerPolyline* polyline );
875875
/** Is emitted when a new composer html has been added to the view*/
876876
void composerHtmlFrameAdded( QgsComposerHtml* html, QgsComposerFrame* frame );
877+
/** Is emitted when a new item group has been added to the view*/
878+
void composerItemGroupAdded( QgsComposerItemGroup* group );
877879
/** Is emitted when new composer label has been added to the view*/
878880
void composerLabelAdded( QgsComposerLabel* label );
879881
/** Is emitted when new composer map has been added to the view*/
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/** A composer command class for grouping / ungrouping composer items.
2+
*
3+
* If mState == Ungrouped, the command owns the group item
4+
*/
5+
class QgsGroupUngroupItemsCommand: QObject, QUndoCommand
6+
{
7+
%TypeHeaderCode
8+
#include "qgsgroupungroupitemscommand.h"
9+
%End
10+
11+
public:
12+
13+
/** Command kind, and state */
14+
enum State
15+
{
16+
Grouped = 0,
17+
Ungrouped
18+
};
19+
20+
/** Create a group or ungroup command
21+
*
22+
* @param s command kind (@see State)
23+
* @param item the group item being created or ungrouped
24+
* @param c the composition including this group
25+
* @param text command label
26+
* @param parent parent command, if any
27+
*
28+
*/
29+
QgsGroupUngroupItemsCommand( State s, QgsComposerItemGroup* item, QgsComposition* c, const QString& text, QUndoCommand* parent = nullptr );
30+
~QgsGroupUngroupItemsCommand();
31+
32+
void redo();
33+
void undo();
34+
35+
signals:
36+
/** Signals addition of an item (the group) */
37+
void itemAdded( QgsComposerItem* item );
38+
/** Signals removal of an item (the group) */
39+
void itemRemoved( QgsComposerItem* item );
40+
41+
};
42+
43+

‎python/core/core.sip

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@
168168
%Include auth/qgsauthmethod.sip
169169

170170
%Include composer/qgsaddremoveitemcommand.sip
171+
%Include composer/qgsgroupungroupitemscommand.sip
171172
%Include composer/qgsaddremovemultiframecommand.sip
172173
%Include composer/qgsatlascomposition.sip
173174
%Include composer/qgscomposerarrow.sip

‎src/core/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,7 @@ SET(QGIS_CORE_SRCS
268268
composer/qgscomposerutils.cpp
269269
composer/qgscomposition.cpp
270270
composer/qgsdoubleboxscalebarstyle.cpp
271+
composer/qgsgroupungroupitemscommand.cpp
271272
composer/qgslegendmodel.cpp
272273
composer/qgsnumericscalebarstyle.cpp
273274
composer/qgspaperitem.cpp
@@ -535,6 +536,7 @@ SET(QGIS_CORE_MOC_HDRS
535536
composer/qgscomposertablev2.h
536537
composer/qgscomposertexttable.h
537538
composer/qgscomposition.h
539+
composer/qgsgroupungroupitemscommand.h
538540
composer/qgslegendmodel.h
539541
composer/qgspaperitem.h
540542

‎src/core/composer/qgsaddremoveitemcommand.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ QgsAddRemoveItemCommand::~QgsAddRemoveItemCommand()
3636

3737
void QgsAddRemoveItemCommand::redo()
3838
{
39+
QUndoCommand::redo(); // call redo() on all childs
3940
if ( mFirstRun )
4041
{
4142
mFirstRun = false;
@@ -46,6 +47,7 @@ void QgsAddRemoveItemCommand::redo()
4647

4748
void QgsAddRemoveItemCommand::undo()
4849
{
50+
QUndoCommand::undo(); // call undo() on all childs, in reverse order
4951
if ( mFirstRun )
5052
{
5153
mFirstRun = false;
@@ -58,6 +60,7 @@ void QgsAddRemoveItemCommand::switchState()
5860
{
5961
if ( mState == Added )
6062
{
63+
// Remove
6164
if ( mComposition )
6265
{
6366
mComposition->itemsModel()->setItemRemoved( mItem );
@@ -68,6 +71,7 @@ void QgsAddRemoveItemCommand::switchState()
6871
}
6972
else //Removed
7073
{
74+
// Add
7175
if ( mComposition )
7276
{
7377
mComposition->itemsModel()->setItemRestored( mItem );

‎src/core/composer/qgscomposeritemgroup.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ QgsComposerItemGroup::~QgsComposerItemGroup()
3636
//loop through group members and remove them from the scene
3737
Q_FOREACH ( QgsComposerItem* item, mItems )
3838
{
39-
if ( !item )
39+
if ( !item || item->isRemoved() )
4040
continue;
4141

4242
//inform model that we are about to remove an item from the scene

‎src/core/composer/qgscomposition.cpp

Lines changed: 53 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
#include "qgscomposerattributetablev2.h"
3636
#include "qgsaddremovemultiframecommand.h"
3737
#include "qgscomposermultiframecommand.h"
38+
#include "qgsgroupungroupitemscommand.h"
3839
#include "qgspaintenginehack.h"
3940
#include "qgspaperitem.h"
4041
#include "qgsproject.h"
@@ -1589,6 +1590,10 @@ void QgsComposition::addItemsFromXML( const QDomElement& elem, const QDomDocumen
15891590
QgsComposerItemGroup *newGroup = new QgsComposerItemGroup( this );
15901591
newGroup->readXML( groupElem, doc );
15911592
addItem( newGroup );
1593+
if ( addUndoCommands )
1594+
{
1595+
pushAddRemoveCommand( newGroup, tr( "Group added" ) );
1596+
}
15921597
}
15931598

15941599
//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
20142019
}
20152020

20162021
QgsComposerItemGroup* itemGroup = new QgsComposerItemGroup( this );
2022+
QgsDebugMsg( QString( "itemgroup created with %1 items (%2 to be added)" ) .arg( itemGroup->items().size() ).arg( items.size() ) );
20172023

20182024
QList<QgsComposerItem*>::iterator itemIter = items.begin();
20192025
for ( ; itemIter != items.end(); ++itemIter )
20202026
{
20212027
itemGroup->addItem( *itemIter );
2028+
QgsDebugMsg( QString( "itemgroup now has %1" )
2029+
.arg( itemGroup->items().size() ) );
20222030
}
20232031

20242032
addItem( itemGroup );
2033+
2034+
QgsGroupUngroupItemsCommand* c = new QgsGroupUngroupItemsCommand( QgsGroupUngroupItemsCommand::Grouped, itemGroup, this, tr( "Items grouped" ) );
2035+
QObject::connect( c, SIGNAL( itemRemoved( QgsComposerItem* ) ), this, SIGNAL( itemRemoved( QgsComposerItem* ) ) );
2036+
QObject::connect( c, SIGNAL( itemAdded( QgsComposerItem* ) ), this, SLOT( sendItemAddedSignal( QgsComposerItem* ) ) );
2037+
2038+
undoStack()->push( c );
2039+
QgsProject::instance()->setDirty( true );
2040+
//QgsDebugMsg( QString( "itemgroup after pushAddRemove has %1" ) .arg( itemGroup->items().size() ) );
2041+
2042+
emit composerItemGroupAdded( itemGroup );
2043+
20252044
return itemGroup;
20262045
}
20272046

@@ -2033,6 +2052,17 @@ QList<QgsComposerItem *> QgsComposition::ungroupItems( QgsComposerItemGroup* gro
20332052
return ungroupedItems;
20342053
}
20352054

2055+
// group ownership transferred to QgsGroupUngroupItemsCommand
2056+
// Call this before removing group items so it can keep note
2057+
// of contents
2058+
QgsGroupUngroupItemsCommand* c = new QgsGroupUngroupItemsCommand( QgsGroupUngroupItemsCommand::Ungrouped, group, this, tr( "Items ungrouped" ) );
2059+
QObject::connect( c, SIGNAL( itemRemoved( QgsComposerItem* ) ), this, SIGNAL( itemRemoved( QgsComposerItem* ) ) );
2060+
QObject::connect( c, SIGNAL( itemAdded( QgsComposerItem* ) ), this, SLOT( sendItemAddedSignal( QgsComposerItem* ) ) );
2061+
2062+
undoStack()->push( c );
2063+
QgsProject::instance()->setDirty( true );
2064+
2065+
20362066
QSet<QgsComposerItem*> groupedItems = group->items();
20372067
QSet<QgsComposerItem*>::iterator itemIt = groupedItems.begin();
20382068
for ( ; itemIt != groupedItems.end(); ++itemIt )
@@ -2041,10 +2071,9 @@ QList<QgsComposerItem *> QgsComposition::ungroupItems( QgsComposerItemGroup* gro
20412071
}
20422072

20432073
group->removeItems();
2044-
removeComposerItem( group, false, false );
20452074

2046-
emit itemRemoved( group );
2047-
delete( group );
2075+
// note: emits itemRemoved
2076+
removeComposerItem( group, false, false );
20482077

20492078
return ungroupedItems;
20502079
}
@@ -2636,6 +2665,7 @@ void QgsComposition::addComposerTableFrame( QgsComposerAttributeTableV2 *table,
26362665
emit composerTableFrameAdded( table, frame );
26372666
}
26382667

2668+
/* public */
26392669
void QgsComposition::removeComposerItem( QgsComposerItem* item, const bool createCommand, const bool removeGroupItems )
26402670
{
26412671
QgsComposerMap* map = dynamic_cast<QgsComposerMap *>( item );
@@ -2644,25 +2674,36 @@ void QgsComposition::removeComposerItem( QgsComposerItem* item, const bool creat
26442674
{
26452675
mItemsModel->setItemRemoved( item );
26462676
removeItem( item );
2677+
emit itemRemoved( item );
2678+
2679+
QgsDebugMsg( QString( "removeComposerItem called, createCommand:%1 removeGroupItems:%2" )
2680+
.arg( createCommand ).arg( removeGroupItems ) );
26472681

26482682
QgsComposerItemGroup* itemGroup = dynamic_cast<QgsComposerItemGroup*>( item );
26492683
if ( itemGroup && removeGroupItems )
26502684
{
2651-
//add add/remove item command for every item in the group
2652-
QUndoCommand* parentCommand = new QUndoCommand( tr( "Remove item group" ) );
2685+
QgsDebugMsg( QString( "itemGroup && removeGroupItems" ) );
26532686

2687+
// Takes ownership of itemGroup
2688+
QgsAddRemoveItemCommand* parentCommand = new QgsAddRemoveItemCommand(
2689+
QgsAddRemoveItemCommand::Removed, itemGroup, this,
2690+
tr( "Remove item group" ) );
2691+
connectAddRemoveCommandSignals( parentCommand );
2692+
2693+
//add add/remove item command for every item in the group
26542694
QSet<QgsComposerItem*> groupedItems = itemGroup->items();
2695+
QgsDebugMsg( QString( "itemGroup contains %1 items" ) .arg( groupedItems.size() ) );
26552696
QSet<QgsComposerItem*>::iterator it = groupedItems.begin();
26562697
for ( ; it != groupedItems.end(); ++it )
26572698
{
2699+
mItemsModel->setItemRemoved( *it );
2700+
removeItem( *it );
26582701
QgsAddRemoveItemCommand* subcommand = new QgsAddRemoveItemCommand( QgsAddRemoveItemCommand::Removed, *it, this, "", parentCommand );
26592702
connectAddRemoveCommandSignals( subcommand );
26602703
emit itemRemoved( *it );
26612704
}
26622705

26632706
undoStack()->push( parentCommand );
2664-
emit itemRemoved( itemGroup );
2665-
delete itemGroup;
26662707
}
26672708
else
26682709
{
@@ -2674,19 +2715,13 @@ void QgsComposition::removeComposerItem( QgsComposerItem* item, const bool creat
26742715
{
26752716
multiFrame = static_cast<QgsComposerFrame*>( item )->multiFrame();
26762717
item->beginItemCommand( tr( "Frame deleted" ) );
2677-
emit itemRemoved( item );
26782718
item->endItemCommand();
26792719
}
26802720
else
26812721
{
2682-
emit itemRemoved( item );
26832722
pushAddRemoveCommand( item, tr( "Item deleted" ), QgsAddRemoveItemCommand::Removed );
26842723
}
26852724
}
2686-
else
2687-
{
2688-
emit itemRemoved( item );
2689-
}
26902725

26912726
//check if there are frames left. If not, remove the multi frame
26922727
if ( frameItem && multiFrame )
@@ -2823,6 +2858,11 @@ void QgsComposition::sendItemAddedSignal( QgsComposerItem* item )
28232858
emit selectedItemChanged( frame );
28242859
return;
28252860
}
2861+
QgsComposerItemGroup* group = dynamic_cast<QgsComposerItemGroup*>( item );
2862+
if ( group )
2863+
{
2864+
emit composerItemGroupAdded( group );
2865+
}
28262866
}
28272867

28282868
void QgsComposition::updatePaperItems()

‎src/core/composer/qgscomposition.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1119,6 +1119,8 @@ class CORE_EXPORT QgsComposition : public QGraphicsScene
11191119
void composerPolylineAdded( QgsComposerPolyline* polyline );
11201120
/** Is emitted when a new composer html has been added to the view*/
11211121
void composerHtmlFrameAdded( QgsComposerHtml* html, QgsComposerFrame* frame );
1122+
/** Is emitted when a new item group has been added to the view*/
1123+
void composerItemGroupAdded( QgsComposerItemGroup* group );
11221124
/** Is emitted when new composer label has been added to the view*/
11231125
void composerLabelAdded( QgsComposerLabel* label );
11241126
/** Is emitted when new composer map has been added to the view*/
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
/***************************************************************************
2+
qgsgroupungroupitemscommand.cpp
3+
---------------------------
4+
begin : 2016-06-09
5+
copyright : (C) 2016 by Sandro Santilli
6+
email : strk at kbt dot io
7+
***************************************************************************/
8+
9+
/***************************************************************************
10+
* *
11+
* This program is free software; you can redistribute it and/or modify *
12+
* it under the terms of the GNU General Public License as published by *
13+
* the Free Software Foundation; either version 2 of the License, or *
14+
* (at your option) any later version. *
15+
* *
16+
***************************************************************************/
17+
18+
#include "qgsgroupungroupitemscommand.h"
19+
#include "qgscomposeritem.h"
20+
#include "qgscomposeritemgroup.h"
21+
#include "qgscomposition.h"
22+
#include "qgsproject.h"
23+
#include "qgscomposermodel.h"
24+
#include "qgslogger.h"
25+
26+
QgsGroupUngroupItemsCommand::QgsGroupUngroupItemsCommand( State s, QgsComposerItemGroup* item, QgsComposition* c, const QString& text, QUndoCommand* parent ):
27+
QUndoCommand( text, parent ), mGroup( item ), mComposition( c ), mState( s ), mFirstRun( true )
28+
{
29+
mItems = mGroup->items();
30+
}
31+
32+
QgsGroupUngroupItemsCommand::~QgsGroupUngroupItemsCommand()
33+
{
34+
if ( mState == Ungrouped )
35+
{
36+
//command class stores the item if ungrouped from the composition
37+
delete mGroup;
38+
}
39+
}
40+
41+
void QgsGroupUngroupItemsCommand::redo()
42+
{
43+
if ( mFirstRun )
44+
{
45+
mFirstRun = false;
46+
return;
47+
}
48+
switchState();
49+
}
50+
51+
void QgsGroupUngroupItemsCommand::undo()
52+
{
53+
if ( mFirstRun )
54+
{
55+
mFirstRun = false;
56+
return;
57+
}
58+
switchState();
59+
}
60+
61+
void QgsGroupUngroupItemsCommand::switchState()
62+
{
63+
if ( mState == Grouped )
64+
{
65+
// ungroup
66+
if ( mComposition )
67+
{
68+
// This is probably redundant
69+
mComposition->itemsModel()->setItemRemoved( mGroup );
70+
mComposition->removeItem( mGroup );
71+
}
72+
mGroup->removeItems();
73+
emit itemRemoved( mGroup );
74+
mState = Ungrouped;
75+
}
76+
else //Ungrouped
77+
{
78+
// group
79+
if ( mComposition )
80+
{
81+
//delete mGroup; mGroup = new QgsComposerItemGroup( mCompoiser );
82+
QSet<QgsComposerItem*>::iterator itemIter = mItems.begin();
83+
for ( ; itemIter != mItems.end(); ++itemIter )
84+
{
85+
mGroup->addItem( *itemIter );
86+
QgsDebugMsg( QString( "itemgroup now has %1" ) .arg( mGroup->items().size() ) );
87+
}
88+
// Add the group
89+
mComposition->itemsModel()->setItemRestored( mGroup );
90+
mComposition->addItem( mGroup );
91+
}
92+
mState = Grouped;
93+
emit itemAdded( mGroup );
94+
}
95+
QgsProject::instance()->setDirty( true );
96+
}

0 commit comments

Comments
 (0)