mirror of
https://github.com/cookiengineer/audacity
synced 2025-05-07 15:22:34 +02:00
392 lines
8.7 KiB
C++
392 lines
8.7 KiB
C++
/**********************************************************************
|
|
|
|
Audacity: A Digital Audio Editor
|
|
|
|
UndoManager.cpp
|
|
|
|
Dominic Mazzoni
|
|
|
|
*******************************************************************//**
|
|
|
|
\class UndoManager
|
|
\brief Works with HistoryWindow to provide the Undo functionality.
|
|
|
|
*//****************************************************************//**
|
|
|
|
\class UndoStackElem
|
|
\brief Holds one item with description and time range for the
|
|
UndoManager
|
|
|
|
*//*******************************************************************/
|
|
|
|
|
|
#include "Audacity.h"
|
|
|
|
#include <wx/hashset.h>
|
|
|
|
#include "BlockFile.h"
|
|
#include "Diags.h"
|
|
#include "Internat.h"
|
|
#include "Sequence.h"
|
|
#include "WaveTrack.h" // temp
|
|
#include "NoteTrack.h" // for Sonify* function declarations
|
|
#include "Diags.h"
|
|
#include "Tags.h"
|
|
|
|
#include "UndoManager.h"
|
|
|
|
using ConstBlockFilePtr = const BlockFile*;
|
|
WX_DECLARE_HASH_SET(ConstBlockFilePtr, wxPointerHash, wxPointerEqual, Set );
|
|
|
|
struct UndoStackElem {
|
|
|
|
UndoStackElem(std::unique_ptr<TrackList> &&tracks_,
|
|
const wxString &description_,
|
|
const wxString &shortDescription_,
|
|
const SelectedRegion &selectedRegion_,
|
|
const std::shared_ptr<Tags> &tags_)
|
|
: state(std::move(tracks_), tags_, selectedRegion_)
|
|
, description(description_)
|
|
, shortDescription(shortDescription_)
|
|
{
|
|
}
|
|
|
|
UndoState state;
|
|
wxString description;
|
|
wxString shortDescription;
|
|
};
|
|
|
|
UndoManager::UndoManager()
|
|
{
|
|
current = -1;
|
|
saved = -1;
|
|
consolidationCount = 0;
|
|
ResetODChangesFlag();
|
|
}
|
|
|
|
UndoManager::~UndoManager()
|
|
{
|
|
ClearStates();
|
|
}
|
|
|
|
void UndoManager::CalculateSpaceUsage()
|
|
{
|
|
TIMER_START( "CalculateSpaceUsage", space_calc );
|
|
TrackListOfKindIterator iter(Track::Wave);
|
|
|
|
space.clear();
|
|
space.resize(stack.size(), 0);
|
|
|
|
Set s1, s2;
|
|
Set *prev = &s1;
|
|
Set *cur = &s2;
|
|
|
|
for (size_t i = 0, cnt = stack.size(); i < cnt; i++)
|
|
{
|
|
// Swap map pointers
|
|
std::swap(cur, prev);
|
|
|
|
// And clean out the NEW current map
|
|
cur->clear();
|
|
|
|
// Scan all tracks at current level
|
|
WaveTrack *wt = (WaveTrack *) iter.First(stack[i]->state.tracks.get());
|
|
while (wt)
|
|
{
|
|
// Scan all clips within current track
|
|
WaveClipList::compatibility_iterator it = wt->GetClipIterator();
|
|
while (it)
|
|
{
|
|
// Scan all blockfiles within current clip
|
|
BlockArray *blocks = it->GetData()->GetSequenceBlockArray();
|
|
for (const auto &block : *blocks)
|
|
{
|
|
BlockFile *file = block.f;
|
|
|
|
// Accumulate space used by the file if the file didn't exist
|
|
// in the previous level
|
|
if (prev->count(file) == 0 && cur->count(file) == 0)
|
|
{
|
|
space[i] += file->GetSpaceUsage().GetValue();
|
|
}
|
|
|
|
// Add file to current set
|
|
cur->insert(file);
|
|
}
|
|
|
|
it = it->GetNext();
|
|
}
|
|
|
|
wt = (WaveTrack *) iter.Next();
|
|
}
|
|
}
|
|
|
|
TIMER_STOP( space_calc );
|
|
}
|
|
|
|
wxLongLong_t UndoManager::GetLongDescription(unsigned int n, wxString *desc,
|
|
wxString *size)
|
|
{
|
|
n -= 1; // 1 based to zero based
|
|
|
|
wxASSERT(n < stack.size());
|
|
wxASSERT(space.size() == stack.size());
|
|
|
|
*desc = stack[n]->description;
|
|
|
|
*size = Internat::FormatSize(space[n]);
|
|
|
|
return space[n];
|
|
}
|
|
|
|
void UndoManager::GetShortDescription(unsigned int n, wxString *desc)
|
|
{
|
|
n -= 1; // 1 based to zero based
|
|
|
|
wxASSERT(n < stack.size());
|
|
|
|
*desc = stack[n]->shortDescription;
|
|
}
|
|
|
|
void UndoManager::SetLongDescription(unsigned int n, const wxString &desc)
|
|
{
|
|
n -= 1;
|
|
|
|
wxASSERT(n < stack.size());
|
|
|
|
stack[n]->description = desc;
|
|
}
|
|
|
|
void UndoManager::RemoveStateAt(int n)
|
|
{
|
|
stack.erase(stack.begin() + n);
|
|
}
|
|
|
|
|
|
void UndoManager::RemoveStates(int num)
|
|
{
|
|
for (int i = 0; i < num; i++) {
|
|
RemoveStateAt(0);
|
|
|
|
current -= 1;
|
|
saved -= 1;
|
|
}
|
|
}
|
|
|
|
void UndoManager::ClearStates()
|
|
{
|
|
RemoveStates(stack.size());
|
|
}
|
|
|
|
unsigned int UndoManager::GetNumStates()
|
|
{
|
|
return stack.size();
|
|
}
|
|
|
|
unsigned int UndoManager::GetCurrentState()
|
|
{
|
|
return current + 1; // the array is 0 based, the abstraction is 1 based
|
|
}
|
|
|
|
bool UndoManager::UndoAvailable()
|
|
{
|
|
return (current > 0);
|
|
}
|
|
|
|
bool UndoManager::RedoAvailable()
|
|
{
|
|
return (current < (int)stack.size() - 1);
|
|
}
|
|
|
|
void UndoManager::ModifyState(const TrackList * l,
|
|
const SelectedRegion &selectedRegion,
|
|
const std::shared_ptr<Tags> &tags)
|
|
{
|
|
if (current == wxNOT_FOUND) {
|
|
return;
|
|
}
|
|
|
|
SonifyBeginModifyState();
|
|
// Delete current -- not necessary, but let's reclaim space early
|
|
stack[current]->state.tracks.reset();
|
|
|
|
// Duplicate
|
|
auto tracksCopy = std::make_unique<TrackList>();
|
|
TrackListConstIterator iter(l);
|
|
const Track *t = iter.First();
|
|
while (t) {
|
|
tracksCopy->Add(t->Duplicate());
|
|
t = iter.Next();
|
|
}
|
|
|
|
// Replace
|
|
stack[current]->state.tracks = std::move(tracksCopy);
|
|
stack[current]->state.tags = tags;
|
|
|
|
stack[current]->state.selectedRegion = selectedRegion;
|
|
SonifyEndModifyState();
|
|
}
|
|
|
|
void UndoManager::PushState(const TrackList * l,
|
|
const SelectedRegion &selectedRegion,
|
|
const std::shared_ptr<Tags> &tags,
|
|
const wxString &longDescription,
|
|
const wxString &shortDescription,
|
|
UndoPush flags)
|
|
{
|
|
unsigned int i;
|
|
|
|
// If consolidate is set to true, group up to 3 identical operations.
|
|
if (((flags & UndoPush::CONSOLIDATE) != UndoPush::MINIMAL) && lastAction == longDescription &&
|
|
consolidationCount < 2) {
|
|
consolidationCount++;
|
|
ModifyState(l, selectedRegion, tags);
|
|
// MB: If the "saved" state was modified by ModifyState, reset
|
|
// it so that UnsavedChanges returns true.
|
|
if (current == saved) {
|
|
saved = -1;
|
|
}
|
|
return;
|
|
}
|
|
|
|
consolidationCount = 0;
|
|
|
|
i = current + 1;
|
|
while (i < stack.size()) {
|
|
RemoveStateAt(i);
|
|
}
|
|
|
|
auto tracksCopy = std::make_unique<TrackList>();
|
|
TrackListConstIterator iter(l);
|
|
const Track *t = iter.First();
|
|
while (t) {
|
|
tracksCopy->Add(t->Duplicate());
|
|
t = iter.Next();
|
|
}
|
|
|
|
// Assume tags was duplicted before any changes.
|
|
// Just save a new shared_ptr to it.
|
|
stack.push_back(
|
|
make_movable<UndoStackElem>
|
|
(std::move(tracksCopy),
|
|
longDescription, shortDescription, selectedRegion, tags)
|
|
);
|
|
|
|
current++;
|
|
|
|
if (saved >= current) {
|
|
saved = -1;
|
|
}
|
|
|
|
lastAction = longDescription;
|
|
}
|
|
|
|
const UndoState &UndoManager::SetStateTo
|
|
(unsigned int n, SelectedRegion *selectedRegion)
|
|
{
|
|
n -= 1;
|
|
|
|
wxASSERT(n < stack.size());
|
|
|
|
current = n;
|
|
|
|
if (current == int(stack.size()-1)) {
|
|
*selectedRegion = stack[current]->state.selectedRegion;
|
|
}
|
|
else {
|
|
*selectedRegion = stack[current + 1]->state.selectedRegion;
|
|
}
|
|
|
|
lastAction = wxT("");
|
|
consolidationCount = 0;
|
|
|
|
return stack[current]->state;
|
|
}
|
|
|
|
const UndoState &UndoManager::Undo(SelectedRegion *selectedRegion)
|
|
{
|
|
wxASSERT(UndoAvailable());
|
|
|
|
current--;
|
|
|
|
*selectedRegion = stack[current]->state.selectedRegion;
|
|
|
|
lastAction = wxT("");
|
|
consolidationCount = 0;
|
|
|
|
return stack[current]->state;
|
|
}
|
|
|
|
const UndoState &UndoManager::Redo(SelectedRegion *selectedRegion)
|
|
{
|
|
wxASSERT(RedoAvailable());
|
|
|
|
current++;
|
|
|
|
*selectedRegion = stack[current]->state.selectedRegion;
|
|
|
|
/*
|
|
if (!RedoAvailable()) {
|
|
*sel0 = stack[current]->sel0;
|
|
*sel1 = stack[current]->sel1;
|
|
}
|
|
else {
|
|
current++;
|
|
*sel0 = stack[current]->sel0;
|
|
*sel1 = stack[current]->sel1;
|
|
current--;
|
|
}
|
|
*/
|
|
|
|
lastAction = wxT("");
|
|
consolidationCount = 0;
|
|
|
|
return stack[current]->state;
|
|
}
|
|
|
|
bool UndoManager::UnsavedChanges()
|
|
{
|
|
return (saved != current) || HasODChangesFlag();
|
|
}
|
|
|
|
void UndoManager::StateSaved()
|
|
{
|
|
saved = current;
|
|
ResetODChangesFlag();
|
|
}
|
|
|
|
// currently unused
|
|
//void UndoManager::Debug()
|
|
//{
|
|
// for (unsigned int i = 0; i < stack.Count(); i++) {
|
|
// TrackListIterator iter(stack[i]->tracks);
|
|
// WaveTrack *t = (WaveTrack *) (iter.First());
|
|
// wxPrintf(wxT("*%d* %s %f\n"), i, (i == (unsigned int)current) ? wxT("-->") : wxT(" "),
|
|
// t ? t->GetEndTime()-t->GetStartTime() : 0);
|
|
// }
|
|
//}
|
|
|
|
///to mark as unsaved changes without changing the state/tracks.
|
|
void UndoManager::SetODChangesFlag()
|
|
{
|
|
mODChangesMutex.Lock();
|
|
mODChanges=true;
|
|
mODChangesMutex.Unlock();
|
|
}
|
|
|
|
bool UndoManager::HasODChangesFlag()
|
|
{
|
|
bool ret;
|
|
mODChangesMutex.Lock();
|
|
ret=mODChanges;
|
|
mODChangesMutex.Unlock();
|
|
return ret;
|
|
}
|
|
|
|
void UndoManager::ResetODChangesFlag()
|
|
{
|
|
mODChangesMutex.Lock();
|
|
mODChanges=false;
|
|
mODChangesMutex.Unlock();
|
|
}
|