Files
kicad-source-mirror/common/commit.cpp
T
Jeff Young 6f389fd320 Tighten parent/child undo/redo architecture.
Always look for pre-existing undo/redo record.  Checking for IsNew()
is less robust and should be avoided.  Also moves the checking to a
location where it will be easier to ensure that it's uniform.

Push get-undo-level-item processing down a level so it is uniformly
called.

Make sure tables & labels are uniformly handled.

Remove incorrect usage of Get/SetGroupId() for storing lastPin
(which we don't use anyway).

Lists of deleted and changed items MUST include the screen pointer.
An item could be changed on one screen but not on another.

Also tightens handling of PCB_NETINFO_T items, which are not in the
view.

Also fixes a bug where there is no increment parameter if you assign
the base increment command to a hotkey.
(This was discovered while testing the above changes.)

Also fixes a bug where delete during a move in PCB Editor did an
undo instead of a delete.
(Again, found while testing above.)

An experiment was also run to collapse shared parts of SCH_EDIT_FRAME
and SYMBOL_EDITOR_FRAME into SCH_BASE_EDIT_FRAME.  However, sharing the
undo code actually increased complexity, and there was very little else
of value in SCH_BASE_EDIT_FRAME (other than the Increment() routines).
2025-08-18 19:20:09 +01:00

231 lines
6.4 KiB
C++

/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright 2016-2017 CERN
* Copyright The KiCad Developers, see AUTHORS.txt for contributors.
*
* @author Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
* @author Maciej Suminski <maciej.suminski@cern.ch>
*
* 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.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you may find one here:
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
* or you may search the http://www.gnu.org website for the version 2 license,
* or you may write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
#include <core/kicad_algo.h>
#include <commit.h>
#include <eda_item.h>
#include <eda_group.h>
#include <macros.h>
COMMIT::COMMIT()
{
}
COMMIT::~COMMIT()
{
for( COMMIT_LINE& ent : m_entries )
delete ent.m_copy;
}
COMMIT& COMMIT::Stage( EDA_ITEM* aItem, CHANGE_TYPE aChangeType, BASE_SCREEN* aScreen, RECURSE_MODE aRecurse )
{
int flags = aChangeType & CHT_FLAGS;
int changeType = aChangeType & CHT_TYPE;
EDA_ITEM* undoItem = undoLevelItem( aItem );
if( undoItem != aItem )
changeType = CHT_MODIFY;
// CHT_MODIFY and CHT_DONE are not compatible
if( changeType == CHT_MODIFY )
wxASSERT( ( flags & CHT_DONE ) == 0 );
switch( changeType )
{
case CHT_ADD:
if( m_addedItems.find( { aItem, aScreen } ) != m_addedItems.end() )
break;
makeEntry( aItem, CHT_ADD | flags, nullptr, aScreen );
break;
case CHT_REMOVE:
if( m_deletedItems.find( { aItem, aScreen } ) != m_deletedItems.end() )
break;
makeEntry( aItem, CHT_REMOVE | flags, makeImage( aItem ), aScreen );
if( EDA_GROUP* parentGroup = aItem->GetParentGroup() )
Modify( parentGroup->AsEdaItem(), aScreen, RECURSE_MODE::NO_RECURSE );
break;
case CHT_MODIFY:
if( m_addedItems.find( { aItem, aScreen } ) != m_addedItems.end() )
break;
if( m_changedItems.find( { undoItem, aScreen } ) != m_changedItems.end() )
break;
makeEntry( undoItem, CHT_MODIFY | flags, makeImage( undoItem ), aScreen );
break;
default:
UNIMPLEMENTED_FOR( undoItem->GetClass() );
}
return *this;
}
COMMIT& COMMIT::Stage( std::vector<EDA_ITEM*> &container, CHANGE_TYPE aChangeType, BASE_SCREEN *aScreen )
{
for( EDA_ITEM* item : container )
Stage( item, aChangeType, aScreen);
return *this;
}
COMMIT& COMMIT::Stage( const PICKED_ITEMS_LIST &aItems, UNDO_REDO aModFlag, BASE_SCREEN *aScreen )
{
for( unsigned int i = 0; i < aItems.GetCount(); i++ )
{
UNDO_REDO change_type = aItems.GetPickedItemStatus( i );
EDA_ITEM* item = aItems.GetPickedItem( i );
if( change_type == UNDO_REDO::UNSPECIFIED )
change_type = aModFlag;
if( EDA_ITEM* copy = aItems.GetPickedItemLink( i ) )
{
assert( change_type == UNDO_REDO::CHANGED );
// There was already a copy created, so use it
Modified( item, copy, aScreen );
}
else
{
Stage( item, convert( change_type ), aScreen );
}
}
return *this;
}
void COMMIT::Unstage( EDA_ITEM* aItem, BASE_SCREEN* aScreen )
{
std::erase_if( m_entries,
[&]( COMMIT_LINE& line )
{
if( line.m_item == aItem && line.m_screen == aScreen )
{
// Only new items which have never been committed can be unstaged
wxASSERT( line.m_item->IsNew() );
delete line.m_item;
delete line.m_copy;
return true;
}
return false;
} );
}
COMMIT& COMMIT::Modified( EDA_ITEM* aItem, EDA_ITEM* aCopy, BASE_SCREEN *aScreen )
{
if( undoLevelItem( aItem ) != aItem )
wxFAIL_MSG( "We've no way to get a copy of the undo level item at this point" );
else
makeEntry( aItem, CHT_MODIFY, aCopy, aScreen );
return *this;
}
int COMMIT::GetStatus( EDA_ITEM* aItem, BASE_SCREEN *aScreen )
{
COMMIT_LINE* entry = findEntry( undoLevelItem( aItem ), aScreen );
return entry ? entry->m_type : 0;
}
void COMMIT::makeEntry( EDA_ITEM* aItem, CHANGE_TYPE aType, EDA_ITEM* aCopy, BASE_SCREEN *aScreen )
{
COMMIT_LINE ent;
ent.m_item = aItem;
ent.m_type = aType;
ent.m_copy = aCopy;
ent.m_screen = aScreen;
// N.B. Do not throw an assertion for multiple changed items. An item can be changed
// multiple times in a single commit such as when importing graphics and grouping them.
switch( aType )
{
case CHT_ADD: m_addedItems.insert( { aItem, aScreen } ); break;
case CHT_REMOVE: m_deletedItems.insert( { aItem, aScreen } ); break;
case CHT_MODIFY: m_changedItems.insert( { aItem, aScreen } ); break;
default: wxFAIL; break;
}
m_entries.push_back( ent );
}
COMMIT::COMMIT_LINE* COMMIT::findEntry( EDA_ITEM* aItem, BASE_SCREEN *aScreen )
{
for( COMMIT_LINE& entry : m_entries )
{
if( entry.m_item == aItem && entry.m_screen == aScreen )
return &entry;
}
return nullptr;
}
CHANGE_TYPE COMMIT::convert( UNDO_REDO aType ) const
{
switch( aType )
{
case UNDO_REDO::NEWITEM: return CHT_ADD;
case UNDO_REDO::DELETED: return CHT_REMOVE;
case UNDO_REDO::CHANGED: return CHT_MODIFY;
default: wxFAIL; return CHT_MODIFY;
}
}
UNDO_REDO COMMIT::convert( CHANGE_TYPE aType ) const
{
switch( aType )
{
case CHT_ADD: return UNDO_REDO::NEWITEM;
case CHT_REMOVE: return UNDO_REDO::DELETED;
case CHT_MODIFY: return UNDO_REDO::CHANGED;
default: wxFAIL; return UNDO_REDO::CHANGED;
}
}