Some undo/redo fixes for multiframe items

This commit is contained in:
Nyall Dawson 2017-11-07 11:31:54 +10:00
parent c6eaf1f7fd
commit f04d438572
13 changed files with 146 additions and 68 deletions

View File

@ -184,35 +184,6 @@ class QgsLayoutMultiFrame: QgsLayoutObject, QgsLayoutUndoObjectInterface
:rtype: bool
%End
QList<QgsLayoutFrame *> frames() const;
%Docstring
Returns a list of all child frames for this multiframe.
.. seealso:: frameCount()
:rtype: list of QgsLayoutFrame
%End
int frameCount() const;
%Docstring
Returns the number of frames associated with this multiframe.
.. seealso:: frames()
:rtype: int
%End
QgsLayoutFrame *frame( int index ) const;
%Docstring
Returns the child frame at a specified ``index`` from the multiframe.
.. seealso:: frameIndex()
:rtype: QgsLayoutFrame
%End
int frameIndex( QgsLayoutFrame *frame ) const;
%Docstring
Returns the index of a ``frame`` within the multiframe.
:return: index for frame if found, -1 if frame not found in multiframe
.. seealso:: frame()
:rtype: int
%End
QgsLayoutFrame *createNewFrame( QgsLayoutFrame *currentFrame, QPointF pos, QSizeF size );
%Docstring
Creates a new frame and adds it to the multi frame and layout.

View File

@ -92,6 +92,31 @@ class QgsLayoutUndoStack : QObject
Notifies the stack that an undo or redo action occurred for a specified ``item``.
%End
void blockCommands( bool blocked );
%Docstring
Sets whether undo commands for the layout should be temporarily blocked.
If ``blocked`` is true, subsequent undo commands will be blocked until a follow-up
call to blockCommands( false ) is made.
Note that calls to blockCommands are stacked, so two calls blocking the commands
will take two calls unblocking commands in order to release the block.
.. seealso:: isBlocked()
%End
bool isBlocked() const;
%Docstring
Returns true if undo commands are currently blocked.
.. seealso:: blockCommands()
:rtype: bool
%End
void push( QUndoCommand *command /Transfer/ );
%Docstring
Manually pushes a ``command`` to the stack, and takes ownership of the command.
%End
signals:
void undoRedoOccurredForItems( const QSet< QString > itemUuids );

View File

@ -41,7 +41,7 @@ QgsLayoutHtmlWidget::QgsLayoutHtmlWidget( QgsLayoutFrame *frame )
connect( mRadioUrlSource, &QRadioButton::clicked, this, &QgsLayoutHtmlWidget::mRadioUrlSource_clicked );
connect( mInsertExpressionButton, &QPushButton::clicked, this, &QgsLayoutHtmlWidget::mInsertExpressionButton_clicked );
connect( mReloadPushButton, &QPushButton::clicked, this, &QgsLayoutHtmlWidget::mReloadPushButton_clicked );
connect( mReloadPushButton2, &QPushButton::clicked, this, &QgsLayoutHtmlWidget::mReloadPushButton2_clicked );
connect( mReloadPushButton2, &QPushButton::clicked, this, &QgsLayoutHtmlWidget::mReloadPushButton_clicked );
connect( mAddFramePushButton, &QPushButton::clicked, this, &QgsLayoutHtmlWidget::mAddFramePushButton_clicked );
connect( mEmptyFrameCheckBox, &QCheckBox::toggled, this, &QgsLayoutHtmlWidget::mEmptyFrameCheckBox_toggled );
connect( mHideEmptyBgCheckBox, &QCheckBox::toggled, this, &QgsLayoutHtmlWidget::mHideEmptyBgCheckBox_toggled );
@ -296,7 +296,6 @@ void QgsLayoutHtmlWidget::mRadioManualSource_clicked( bool checked )
blockSignals( true );
mHtml->beginCommand( tr( "Change HTML Source" ) );
mHtml->setContentMode( checked ? QgsLayoutItemHtml::ManualHtml : QgsLayoutItemHtml::Url );
mHtml->endCommand();
blockSignals( false );
mHtmlEditor->setEnabled( checked );
@ -305,6 +304,7 @@ void QgsLayoutHtmlWidget::mRadioManualSource_clicked( bool checked )
mFileToolButton->setEnabled( !checked );
mHtml->loadHtml();
mHtml->endCommand();
}
void QgsLayoutHtmlWidget::mRadioUrlSource_clicked( bool checked )
@ -317,7 +317,6 @@ void QgsLayoutHtmlWidget::mRadioUrlSource_clicked( bool checked )
blockSignals( true );
mHtml->beginCommand( tr( "Change HTML Source" ) );
mHtml->setContentMode( checked ? QgsLayoutItemHtml::Url : QgsLayoutItemHtml::ManualHtml );
mHtml->endCommand();
blockSignals( false );
mHtmlEditor->setEnabled( !checked );
@ -326,6 +325,7 @@ void QgsLayoutHtmlWidget::mRadioUrlSource_clicked( bool checked )
mFileToolButton->setEnabled( checked );
mHtml->loadHtml();
mHtml->endCommand();
}
void QgsLayoutHtmlWidget::mInsertExpressionButton_clicked()
@ -390,17 +390,11 @@ void QgsLayoutHtmlWidget::mReloadPushButton_clicked()
return;
}
if ( mHtml->layout() )
mHtml->layout()->undoStack()->blockCommands( true );
mHtml->loadHtml();
}
void QgsLayoutHtmlWidget::mReloadPushButton2_clicked()
{
if ( !mHtml )
{
return;
}
mHtml->loadHtml();
if ( mHtml->layout() )
mHtml->layout()->undoStack()->blockCommands( false );
}
void QgsLayoutHtmlWidget::mAddFramePushButton_clicked()

View File

@ -53,7 +53,6 @@ class QgsLayoutHtmlWidget: public QgsLayoutItemBaseWidget, private Ui::QgsLayout
void mInsertExpressionButton_clicked();
void mReloadPushButton_clicked();
void mReloadPushButton2_clicked();
void mAddFramePushButton_clicked();
void mEmptyFrameCheckBox_toggled( bool checked );
void mHideEmptyBgCheckBox_toggled( bool checked );

View File

@ -42,7 +42,7 @@ QgsLayout::QgsLayout( QgsProject *project )
QgsLayout::~QgsLayout()
{
// no need for undo commands when we're destroying the layout
mBlockUndoCommandCount++;
mUndoStack->blockCommands( true );
deleteAndRemoveMultiFrames();
@ -410,14 +410,14 @@ void QgsLayout::addLayoutItem( QgsLayoutItem *item )
{
undoText = tr( "Create Item" );
}
if ( mBlockUndoCommandCount == 0 )
mUndoStack->stack()->push( new QgsLayoutItemAddItemCommand( item, undoText ) );
if ( !mUndoStack->isBlocked() )
mUndoStack->push( new QgsLayoutItemAddItemCommand( item, undoText ) );
}
void QgsLayout::removeLayoutItem( QgsLayoutItem *item )
{
std::unique_ptr< QgsLayoutItemDeleteUndoCommand > deleteCommand;
if ( mBlockUndoCommandCount == 0 )
if ( !mUndoStack->isBlocked() )
{
mUndoStack->beginMacro( tr( "Delete Items" ) );
deleteCommand.reset( new QgsLayoutItemDeleteUndoCommand( item, tr( "Delete Item" ) ) );
@ -425,7 +425,7 @@ void QgsLayout::removeLayoutItem( QgsLayoutItem *item )
removeLayoutItemPrivate( item );
if ( deleteCommand )
{
mUndoStack->stack()->push( deleteCommand.release() );
mUndoStack->push( deleteCommand.release() );
mUndoStack->endMacro();
}
}
@ -518,7 +518,7 @@ QgsLayoutItemGroup *QgsLayout::groupItems( const QList<QgsLayoutItem *> &items )
addLayoutItem( itemGroup.release() );
std::unique_ptr< QgsLayoutItemGroupUndoCommand > c( new QgsLayoutItemGroupUndoCommand( QgsLayoutItemGroupUndoCommand::Grouped, returnGroup, this, tr( "Group Items" ) ) );
mUndoStack->stack()->push( c.release() );
mUndoStack->push( c.release() );
mProject->setDirty( true );
#if 0
@ -542,7 +542,7 @@ QList<QgsLayoutItem *> QgsLayout::ungroupItems( QgsLayoutItemGroup *group )
// Call this before removing group items so it can keep note
// of contents
std::unique_ptr< QgsLayoutItemGroupUndoCommand > c( new QgsLayoutItemGroupUndoCommand( QgsLayoutItemGroupUndoCommand::Ungrouped, group, this, tr( "Ungroup Items" ) ) );
mUndoStack->stack()->push( c.release() );
mUndoStack->push( c.release() );
mProject->setDirty( true );

View File

@ -559,8 +559,6 @@ class CORE_EXPORT QgsLayout : public QGraphicsScene, public QgsExpressionContext
std::unique_ptr< QgsLayoutUndoStack > mUndoStack;
QgsLayoutExporter mExporter;
int mBlockUndoCommandCount = 0;
//! List of multiframe objects
QSet<QgsLayoutMultiFrame *> mMultiFrames;

View File

@ -163,7 +163,7 @@ void QgsLayoutItem::setVisibility( const bool visible )
if ( command )
{
command->saveAfterState();
mLayout->undoStack()->stack()->push( command.release() );
mLayout->undoStack()->push( command.release() );
}
//inform model that visibility has changed

View File

@ -168,7 +168,7 @@ void QgsLayoutItemGroup::attemptMove( const QgsLayoutPoint &point, bool useRefer
if ( command )
{
command->saveAfterState();
mLayout->undoStack()->stack()->push( command.release() );
mLayout->undoStack()->push( command.release() );
}
}
//lastly move group item itself
@ -220,7 +220,7 @@ void QgsLayoutItemGroup::attemptResize( const QgsLayoutSize &size, bool includes
if ( command )
{
command->saveAfterState();
mLayout->undoStack()->stack()->push( command.release() );
mLayout->undoStack()->push( command.release() );
}
}
QgsLayoutItem::attemptResize( size );

View File

@ -74,8 +74,10 @@ void QgsLayoutMultiFrame::setResizeMode( ResizeMode mode )
{
if ( mode != mResizeMode )
{
mLayout->undoStack()->beginMacro( tr( "Change Resize Mode" ) );
mResizeMode = mode;
recalculateFrameSizes();
mLayout->undoStack()->endMacro();
emit changed();
}
}
@ -101,7 +103,7 @@ void QgsLayoutMultiFrame::recalculateFrameSizes()
}
if ( mBlockUndoCommands )
mLayout->mBlockUndoCommandCount++;
mLayout->undoStack()->blockCommands( true );
double currentY = 0;
double currentHeight = 0;
@ -213,7 +215,7 @@ void QgsLayoutMultiFrame::recalculateFrameSizes()
}
if ( mBlockUndoCommands )
mLayout->mBlockUndoCommandCount--;
mLayout->undoStack()->blockCommands( false );
}
void QgsLayoutMultiFrame::recalculateFrameRects()
@ -376,14 +378,14 @@ void QgsLayoutMultiFrame::removeFrame( int i, const bool removeEmptyPages )
mIsRecalculatingSize = true;
int pageNumber = frameItem->page();
//remove item, but don't create undo command
mLayout->mBlockUndoCommandCount++;
mLayout->undoStack()->blockCommands( true );
mLayout->removeLayoutItem( frameItem );
//if frame was the only item on the page, remove the page
if ( removeEmptyPages && mLayout->pageCollection()->pageIsEmpty( pageNumber ) )
{
mLayout->pageCollection()->deletePage( pageNumber );
}
mLayout->mBlockUndoCommandCount--;
mLayout->undoStack()->blockCommands( false );
mIsRecalculatingSize = false;
}
mFrameItems.removeAt( i );
@ -402,12 +404,12 @@ void QgsLayoutMultiFrame::deleteFrames()
mBlockUpdates = true;
ResizeMode bkResizeMode = mResizeMode;
mResizeMode = UseExistingFrames;
mLayout->mBlockUndoCommandCount++;
mLayout->undoStack()->blockCommands( true );
for ( QgsLayoutFrame *frame : qgis::as_const( mFrameItems ) )
{
mLayout->removeLayoutItem( frame );
}
mLayout->mBlockUndoCommandCount--;
mLayout->undoStack()->blockCommands( false );
mFrameItems.clear();
mResizeMode = bkResizeMode;
mBlockUpdates = false;
@ -460,6 +462,7 @@ bool QgsLayoutMultiFrame::readXml( const QDomElement &element, const QDomDocumen
}
mBlockUndoCommands = true;
mLayout->undoStack()->blockCommands( true );
readObjectPropertiesFromElement( element, doc, context );
@ -491,6 +494,7 @@ bool QgsLayoutMultiFrame::readXml( const QDomElement &element, const QDomDocumen
bool result = readPropertiesFromElement( element, doc, context );
mBlockUndoCommands = false;
mLayout->undoStack()->blockCommands( false );
return result;
}

View File

@ -29,12 +29,14 @@ QgsLayoutUndoStack::QgsLayoutUndoStack( QgsLayout *layout )
void QgsLayoutUndoStack::beginMacro( const QString &commandText )
{
mUndoStack->beginMacro( commandText );
if ( mBlockedCommands == 0 )
mUndoStack->beginMacro( commandText );
}
void QgsLayoutUndoStack::endMacro()
{
mUndoStack->endMacro();
if ( mBlockedCommands == 0 )
mUndoStack->endMacro();
}
void QgsLayoutUndoStack::beginCommand( QgsLayoutUndoObjectInterface *object, const QString &commandText, int id )
@ -54,13 +56,13 @@ void QgsLayoutUndoStack::endCommand()
return;
mActiveCommands.back()->saveAfterState();
if ( mActiveCommands.back()->containsChange() ) //protect against empty commands
if ( mBlockedCommands == 0 && mActiveCommands.back()->containsChange() ) //protect against empty commands
{
mUndoStack->push( mActiveCommands.back().release() );
mActiveCommands.pop_back();
mLayout->project()->setDirty( true );
}
mActiveCommands.pop_back();
}
void QgsLayoutUndoStack::cancelCommand()
@ -81,6 +83,32 @@ void QgsLayoutUndoStack::notifyUndoRedoOccurred( QgsLayoutItem *item )
mUndoRedoOccurredItemUuids.insert( item->uuid() );
}
void QgsLayoutUndoStack::blockCommands( bool blocked )
{
if ( blocked )
{
mBlockedCommands++;
}
else
{
if ( mBlockedCommands > 0 )
mBlockedCommands--;
}
}
bool QgsLayoutUndoStack::isBlocked() const
{
return mBlockedCommands > 0;
}
void QgsLayoutUndoStack::push( QUndoCommand *cmd )
{
if ( mBlockedCommands > 0 )
delete cmd;
else
mUndoStack->push( cmd );
}
void QgsLayoutUndoStack::indexChanged()
{
if ( mUndoRedoOccurredItemUuids.empty() )

View File

@ -107,6 +107,30 @@ class CORE_EXPORT QgsLayoutUndoStack : public QObject
*/
void notifyUndoRedoOccurred( QgsLayoutItem *item );
/**
* Sets whether undo commands for the layout should be temporarily blocked.
*
* If \a blocked is true, subsequent undo commands will be blocked until a follow-up
* call to blockCommands( false ) is made.
*
* Note that calls to blockCommands are stacked, so two calls blocking the commands
* will take two calls unblocking commands in order to release the block.
*
* \see isBlocked()
*/
void blockCommands( bool blocked );
/**
* Returns true if undo commands are currently blocked.
* \see blockCommands()
*/
bool isBlocked() const;
/**
* Manually pushes a \a command to the stack, and takes ownership of the command.
*/
void push( QUndoCommand *command SIP_TRANSFER );
signals:
/**
@ -129,6 +153,8 @@ class CORE_EXPORT QgsLayoutUndoStack : public QObject
QSet< QString > mUndoRedoOccurredItemUuids;
int mBlockedCommands = 0;
#ifdef SIP_RUN
QgsLayoutUndoStack( const QgsLayoutUndoStack &other );
#endif

View File

@ -616,7 +616,7 @@ void QgsLayoutMouseHandles::mouseReleaseEvent( QGraphicsSceneMouseEvent *event )
item->attemptMove( itemPos );
command->saveAfterState();
mLayout->undoStack()->stack()->push( command.release() );
mLayout->undoStack()->push( command.release() );
}
mLayout->undoStack()->endMacro();
}
@ -662,7 +662,7 @@ void QgsLayoutMouseHandles::mouseReleaseEvent( QGraphicsSceneMouseEvent *event )
item->attemptMove( itemPos, false, true );
command->saveAfterState();
mLayout->undoStack()->stack()->push( command.release() );
mLayout->undoStack()->push( command.release() );
}
mLayout->undoStack()->endMacro();
}

View File

@ -469,6 +469,39 @@ void TestQgsLayout::undoRedoOccurred()
items = qvariant_cast< QSet< QString > >( spyOccurred.at( 3 ).at( 0 ) );
QCOMPARE( items, QSet< QString >() << item->uuid() << item2->uuid() );
// blocking undo
int before = l.undoStack()->stack()->count();
item->setId( "xxx" );
QCOMPARE( l.undoStack()->stack()->count(), before + 1 );
l.undoStack()->blockCommands( true );
QVERIFY( l.undoStack()->isBlocked() );
item->setId( "yyy" );
QCOMPARE( l.undoStack()->stack()->count(), before + 1 ); // no new command
l.undoStack()->blockCommands( true ); // second stacked command
QVERIFY( l.undoStack()->isBlocked() );
item->setId( "ZZZ" );
QCOMPARE( l.undoStack()->stack()->count(), before + 1 ); // no new command
l.undoStack()->blockCommands( false ); // one stacked command left
QVERIFY( l.undoStack()->isBlocked() );
item->setId( "sss" );
QCOMPARE( l.undoStack()->stack()->count(), before + 1 ); // no new command
l.undoStack()->blockCommands( false ); // unblocked
QVERIFY( !l.undoStack()->isBlocked() );
item->setId( "ttt" );
QCOMPARE( l.undoStack()->stack()->count(), before + 2 ); // new command
l.undoStack()->blockCommands( false ); // don't allow negative stack size
QVERIFY( !l.undoStack()->isBlocked() );
item->setId( "uuu" );
QCOMPARE( l.undoStack()->stack()->count(), before + 3 ); // new command
l.undoStack()->blockCommands( true ); // should be blocked again
QVERIFY( l.undoStack()->isBlocked() );
item->setId( "vvv" );
QCOMPARE( l.undoStack()->stack()->count(), before + 3 ); // no new command
// blocked macro
l.undoStack()->beginMacro( "macro" );
item->setId( "lll" );
l.undoStack()->endMacro();
QCOMPARE( l.undoStack()->stack()->count(), before + 3 ); // no new command
}
void TestQgsLayout::itemsOnPage()