Audacity  3.2.0
UndoManager.cpp
Go to the documentation of this file.
1 /**********************************************************************
2 
3  Audacity: A Digital Audio Editor
4 
5  UndoManager.cpp
6 
7  Dominic Mazzoni
8 
9 *******************************************************************//****************************************************************//*******************************************************************/
21 
22 
23 
24 #include "UndoManager.h"
25 
26 #include <wx/hashset.h>
27 
28 #include "Clipboard.h"
29 #include "Diags.h"
30 #include "Project.h"
31 #include "SampleBlock.h"
32 #include "Sequence.h"
33 #include "WaveTrack.h" // temp
34 //#include "NoteTrack.h" // for Sonify* function declarations
35 #include "Diags.h"
36 #include "Tags.h"
37 #include "TransactionScope.h"
38 #include "widgets/ProgressDialog.h"
39 
40 
41 #include <unordered_set>
42 
43 wxDEFINE_EVENT(EVT_UNDO_PUSHED, wxCommandEvent);
44 wxDEFINE_EVENT(EVT_UNDO_MODIFIED, wxCommandEvent);
45 wxDEFINE_EVENT(EVT_UNDO_RENAMED, wxCommandEvent);
46 wxDEFINE_EVENT(EVT_UNDO_OR_REDO, wxCommandEvent);
47 wxDEFINE_EVENT(EVT_UNDO_RESET, wxCommandEvent);
48 wxDEFINE_EVENT(EVT_UNDO_PURGE, wxCommandEvent);
49 
50 using SampleBlockID = long long;
51 
53  [](AudacityProject &project)
54  { return std::make_unique<UndoManager>( project ); }
55 };
56 
58 {
59  return project.AttachedObjects::Get< UndoManager >( key );
60 }
61 
63 {
64  return Get( const_cast< AudacityProject & >( project ) );
65 }
66 
68  : mProject{ project }
69 {
70  current = -1;
71  saved = -1;
72 }
73 
75 {
76  wxASSERT( stack.empty() );
77 }
78 
79 namespace {
80  SpaceArray::value_type
82  {
83  SpaceArray::value_type result = 0;
84  //TIMER_START( "CalculateSpaceUsage", space_calc );
86  tracks,
88  &seen
89  );
90  return result;
91  }
92 }
93 
95 {
96  space.clear();
97  space.resize(stack.size(), 0);
98 
99  SampleBlockIDSet seen;
100 
101  // After copies and pastes, a block file may be used in more than
102  // one place in one undo history state, and it may be used in more than
103  // one undo history state. It might even be used in two states, but not
104  // in another state that is between them -- as when you have state A,
105  // then make a cut to get state B, but then paste it back into state C.
106 
107  // So be sure to count each block file once only, in the last undo item that
108  // contains it.
109 
110  // Why the last and not the first? Because the user of the History dialog
111  // may DELETE undo states, oldest first. To reclaim disk space you must
112  // DELETE all states containing the block file. So the block file's
113  // contribution to space usage should be counted only in that latest state.
114 
115  for (size_t nn = stack.size(); nn--;)
116  {
117  // Scan all tracks at current level
118  auto &tracks = *stack[nn]->state.tracks;
119  space[nn] = CalculateUsage(tracks, seen);
120  }
121 
122  // Count the usage of the clipboard separately, using another set. Do not
123  // multiple-count any block occurring multiple times within the clipboard.
124  seen.clear();
126  Clipboard::Get().GetTracks(), seen);
127 
128  //TIMER_STOP( space_calc );
129 }
130 
132  unsigned int n, TranslatableString *desc, TranslatableString *size)
133 {
134  wxASSERT(n < stack.size());
135  wxASSERT(space.size() == stack.size());
136 
137  *desc = stack[n]->description;
138 
140 
141  return space[n];
142 }
143 
145 {
146  wxASSERT(n < stack.size());
147 
148  *desc = stack[n]->shortDescription;
149 }
150 
152  unsigned int n, const TranslatableString &desc)
153 {
154  n -= 1;
155 
156  wxASSERT(n < stack.size());
157 
158  stack[n]->description = desc;
159 }
160 
162 {
163  // Remove the state from the array first, and destroy it at function exit.
164  // Because in case of callbacks from destruction of Sample blocks, there
165  // might be a yield to GUI and other events might inspect the undo stack
166  // (such as history window update). Don't expose an inconsistent stack
167  // state.
168  auto iter = stack.begin() + n;
169  auto state = std::move(*iter);
170  stack.erase(iter);
171 }
172 
173 
175 
176 size_t UndoManager::EstimateRemovedBlocks(size_t begin, size_t end)
177 {
178  if (begin == end)
179  return 0;
180 
181  // Collect ids that survive
182  SampleBlockIDSet wontDelete;
183  auto f = [&](const auto &p){
184  InspectBlocks(*p->state.tracks, {}, &wontDelete);
185  };
186  auto first = stack.begin(), last = stack.end();
187  std::for_each( first, first + begin, f );
188  std::for_each( first + end, last, f );
189  if (saved >= 0)
190  std::for_each( first + saved, first + saved + 1, f );
191  InspectBlocks(TrackList::Get(mProject), {}, &wontDelete);
192 
193  // Collect ids that won't survive (and are not negative pseudo ids)
194  SampleBlockIDSet seen, mayDelete;
195  std::for_each( first + begin, first + end, [&](const auto &p){
196  auto &tracks = *p->state.tracks;
197  InspectBlocks(tracks, [&]( const SampleBlock &block ){
198  auto id = block.GetBlockID();
199  if ( id > 0 && !wontDelete.count( id ) )
200  mayDelete.insert( id );
201  },
202  &seen);
203  } );
204  return mayDelete.size();
205 }
206 
207 void UndoManager::RemoveStates(size_t begin, size_t end)
208 {
209  // Install a callback function that updates a progress indicator
210  unsigned long long nToDelete = EstimateRemovedBlocks(begin, end),
211  nDeleted = 0;
212  ProgressDialog dialog{ XO("Progress"), XO("Discarding undo/redo history"),
214  };
215  auto callback = [&](const SampleBlock &){
216  dialog.Update(++nDeleted, nToDelete);
217  };
218  auto &trackFactory = WaveTrackFactory::Get( mProject );
219  auto &pSampleBlockFactory = trackFactory.GetSampleBlockFactory();
220  auto prevCallback =
221  pSampleBlockFactory->SetBlockDeletionCallback(callback);
222  auto cleanup = finally([&]{ pSampleBlockFactory->SetBlockDeletionCallback( prevCallback ); });
223 
224  // Wrap the whole in a savepoint for better performance
225  TransactionScope trans{mProject, "DiscardingUndoStates"};
226 
227  for (size_t ii = begin; ii < end; ++ii) {
228  RemoveStateAt(begin);
229 
230  if (current > begin)
231  --current;
232  if (saved > static_cast<int>(begin))
233  --saved;
234  }
235 
236  // Success, commit the savepoint
237  trans.Commit();
238 
239  if (begin != end)
240  // wxWidgets will own the event object
241  mProject.QueueEvent( safenew wxCommandEvent{ EVT_UNDO_PURGE } );
242 
243  // Check sanity
244  wxASSERT_MSG(
245  nDeleted == 0 || // maybe bypassing all deletions
246  nDeleted == nToDelete, "Block count was misestimated");
247 }
248 
250 {
251  RemoveStates(0, stack.size());
252  current = -1;
253  saved = -1;
254 }
255 
257 {
258  return stack.size();
259 }
260 
262 {
263  return current;
264 }
265 
267 {
268  return (current > 0);
269 }
270 
272 {
273  return (current < (int)stack.size() - 1);
274 }
275 
277  const SelectedRegion &selectedRegion,
278  const std::shared_ptr<Tags> &tags)
279 {
280  if (current == wxNOT_FOUND) {
281  return;
282  }
283 
284 // SonifyBeginModifyState();
285  // Delete current -- not necessary, but let's reclaim space early
286  stack[current]->state.tracks.reset();
287 
288  // Duplicate
289  auto tracksCopy = TrackList::Create( nullptr );
290  for (auto t : *l) {
291  if ( t->GetId() == TrackId{} )
292  // Don't copy a pending added track
293  continue;
294  tracksCopy->Add(t->Duplicate());
295  }
296 
297  // Replace
298  stack[current]->state.tracks = std::move(tracksCopy);
299  stack[current]->state.tags = tags;
300 
301  stack[current]->state.selectedRegion = selectedRegion;
302 // SonifyEndModifyState();
303 
304  // wxWidgets will own the event object
305  mProject.QueueEvent( safenew wxCommandEvent{ EVT_UNDO_MODIFIED } );
306 }
307 
308 void UndoManager::RenameState( int state,
309  const TranslatableString &longDescription,
310  const TranslatableString &shortDescription)
311 {
312  if (state >= 0 && state < stack.size() ) {
313  auto &theState = *stack[state];
314  theState.description = longDescription;
315  theState.shortDescription = shortDescription;
316 
317  // wxWidgets will own the event object
318  mProject.QueueEvent( safenew wxCommandEvent{ EVT_UNDO_RENAMED } );
319  }
320 }
321 
323  const SelectedRegion &selectedRegion,
324  const std::shared_ptr<Tags> &tags,
325  const TranslatableString &longDescription,
326  const TranslatableString &shortDescription,
327  UndoPush flags)
328 {
329  if ( (flags & UndoPush::CONSOLIDATE) != UndoPush::NONE &&
330  // compare full translations not msgids!
331  lastAction.Translation() == longDescription.Translation() &&
332  mayConsolidate ) {
333  ModifyState(l, selectedRegion, tags);
334  // MB: If the "saved" state was modified by ModifyState, reset
335  // it so that UnsavedChanges returns true.
336  if (current == saved) {
337  saved = -1;
338  }
339  return;
340  }
341 
342  auto tracksCopy = TrackList::Create( nullptr );
343  for (auto t : *l) {
344  if ( t->GetId() == TrackId{} )
345  // Don't copy a pending added track
346  continue;
347  tracksCopy->Add(t->Duplicate());
348  }
349 
350  mayConsolidate = true;
351 
352  AbandonRedo();
353 
354  // Assume tags was duplicated before any changes.
355  // Just save a NEW shared_ptr to it.
356  stack.push_back(
357  std::make_unique<UndoStackElem>
358  (std::move(tracksCopy),
359  longDescription, shortDescription, selectedRegion, tags)
360  );
361 
362  current++;
363 
364  lastAction = longDescription;
365 
366  // wxWidgets will own the event object
367  mProject.QueueEvent( safenew wxCommandEvent{ EVT_UNDO_PUSHED } );
368 }
369 
371 {
372  if (saved > current) {
373  saved = -1;
374  }
375  RemoveStates( current + 1, stack.size() );
376 }
377 
378 void UndoManager::SetStateTo(unsigned int n, const Consumer &consumer)
379 {
380  wxASSERT(n < stack.size());
381 
382  current = n;
383 
384  lastAction = {};
385  mayConsolidate = false;
386 
387  consumer( *stack[current] );
388 
389  // wxWidgets will own the event object
390  mProject.QueueEvent( safenew wxCommandEvent{ EVT_UNDO_RESET } );
391 }
392 
393 void UndoManager::Undo(const Consumer &consumer)
394 {
395  wxASSERT(UndoAvailable());
396 
397  current--;
398 
399  lastAction = {};
400  mayConsolidate = false;
401 
402  consumer( *stack[current] );
403 
404  // wxWidgets will own the event object
405  mProject.QueueEvent( safenew wxCommandEvent{ EVT_UNDO_OR_REDO } );
406 }
407 
408 void UndoManager::Redo(const Consumer &consumer)
409 {
410  wxASSERT(RedoAvailable());
411 
412  current++;
413 
414  /*
415  if (!RedoAvailable()) {
416  *sel0 = stack[current]->sel0;
417  *sel1 = stack[current]->sel1;
418  }
419  else {
420  current++;
421  *sel0 = stack[current]->sel0;
422  *sel1 = stack[current]->sel1;
423  current--;
424  }
425  */
426 
427  lastAction = {};
428  mayConsolidate = false;
429 
430  consumer( *stack[current] );
431 
432  // wxWidgets will own the event object
433  mProject.QueueEvent( safenew wxCommandEvent{ EVT_UNDO_OR_REDO } );
434 }
435 
436 void UndoManager::VisitStates( const Consumer &consumer, bool newestFirst )
437 {
438  auto fn = [&]( decltype(stack[0]) &ptr ){ consumer( *ptr ); };
439  if (newestFirst)
440  std::for_each(stack.rbegin(), stack.rend(), fn);
441  else
442  std::for_each(stack.begin(), stack.end(), fn);
443 }
444 
446  const Consumer &consumer, size_t begin, size_t end )
447 {
448  auto size = stack.size();
449  if (begin < end) {
450  end = std::min(end, size);
451  for (auto ii = begin; ii < end; ++ii)
452  consumer(*stack[ii]);
453  }
454  else {
455  if (size == 0)
456  return;
457  begin = std::min(begin, size - 1);
458  for (auto ii = begin; ii > end; --ii)
459  consumer(*stack[ii]);
460  }
461 }
462 
464 {
465  return (saved != current);
466 }
467 
469 {
470  saved = current;
471 }
472 
474 {
475  return saved;
476 }
477 
478 // currently unused
479 //void UndoManager::Debug()
480 //{
481 // for (unsigned int i = 0; i < stack.size(); i++) {
482 // for (auto t : stack[i]->tracks->Any())
483 // wxPrintf(wxT("*%d* %s %f\n"),
484 // i, (i == (unsigned int)current) ? wxT("-->") : wxT(" "),
485 // t ? t->GetEndTime()-t->GetStartTime() : 0);
486 // }
487 //}
488 
size
size_t size
Definition: ffmpeg-2.3.6-single-header.h:412
WaveTrack.h
TranslatableString
Holds a msgid for the translation catalog; may also bind format arguments.
Definition: TranslatableString.h:32
TransactionScope.h
WaveTrackFactory::Get
static WaveTrackFactory & Get(AudacityProject &project)
Definition: WaveTrack.cpp:2799
SampleBlock::GetBlockID
virtual SampleBlockID GetBlockID() const =0
SampleBlockIDSet
std::unordered_set< SampleBlockID > SampleBlockIDSet
Definition: WaveTrack.h:589
UndoManager::VisitStates
void VisitStates(const Consumer &consumer, bool newestFirst)
Give read-only access to all states.
Definition: UndoManager.cpp:436
fn
static const auto fn
Definition: WaveformView.cpp:1122
UndoManager::saved
int saved
Definition: UndoManager.h:217
UndoManager::CalculateSpaceUsage
void CalculateSpaceUsage()
Definition: UndoManager.cpp:94
UndoManager::Undo
void Undo(const Consumer &consumer)
Definition: UndoManager.cpp:393
UndoManager::Consumer
std::function< void(const UndoStackElem &) > Consumer
Definition: UndoManager.h:180
Project.h
UndoManager::mProject
AudacityProject & mProject
Definition: UndoManager.h:214
UndoManager::RemoveStateAt
void RemoveStateAt(int n)
Definition: UndoManager.cpp:161
UndoManager::lastAction
TranslatableString lastAction
Definition: UndoManager.h:220
UndoManager::mayConsolidate
bool mayConsolidate
Definition: UndoManager.h:221
Clipboard.h
pdlgHideStopButton
@ pdlgHideStopButton
Definition: ProgressDialog.h:38
TrackList
A flat linked list of tracks supporting Add, Remove, Clear, and Contains, serialization of the list o...
Definition: Track.h:1288
UndoManager::RedoAvailable
bool RedoAvailable()
Definition: UndoManager.cpp:271
UndoPush::NONE
@ NONE
XO
#define XO(s)
Definition: Internat.h:31
UndoManager::UndoAvailable
bool UndoAvailable()
Definition: UndoManager.cpp:266
ProgressDialog.h
UndoManager::UndoManager
UndoManager(AudacityProject &project)
Definition: UndoManager.cpp:67
InspectBlocks
void InspectBlocks(const TrackList &tracks, BlockInspector inspector, SampleBlockIDSet *pIDs)
Definition: WaveTrack.cpp:2780
ClientData::Site::RegisteredFactory
Client code makes static instance from a factory of attachments; passes it to Get or Find as a retrie...
Definition: ClientData.h:266
desc
const TranslatableString desc
Definition: ExportPCM.cpp:58
Tags.h
SampleBlockID
long long SampleBlockID
Definition: ProjectFileIO.h:41
UndoManager::AbandonRedo
void AbandonRedo()
Definition: UndoManager.cpp:370
SampleBlock.h
Diags.h
ProgressDialog
ProgressDialog Class.
Definition: ProgressDialog.h:51
key
static const AudacityProject::AttachedObjects::RegisteredFactory key
Definition: UndoManager.cpp:52
UndoManager::SetStateTo
void SetStateTo(unsigned int n, const Consumer &consumer)
Definition: UndoManager.cpp:378
anonymous_namespace{UndoManager.cpp}::CalculateUsage
SpaceArray::value_type CalculateUsage(const TrackList &tracks, SampleBlockIDSet &seen)
Definition: UndoManager.cpp:81
UndoManager::EstimateRemovedBlocks
size_t EstimateRemovedBlocks(size_t begin, size_t end)
Just to find a denominator for a progress indicator.
Definition: UndoManager.cpp:176
Internat::FormatSize
static TranslatableString FormatSize(wxLongLong size)
Convert a number to a string while formatting it in bytes, KB, MB, GB.
Definition: Internat.cpp:204
UndoManager::stack
UndoStack stack
Definition: UndoManager.h:218
UndoManager::SetLongDescription
void SetLongDescription(unsigned int n, const TranslatableString &desc)
Definition: UndoManager.cpp:151
UndoManager::PushState
void PushState(const TrackList *l, const SelectedRegion &selectedRegion, const std::shared_ptr< Tags > &tags, const TranslatableString &longDescription, const TranslatableString &shortDescription, UndoPush flags=UndoPush::NONE)
Definition: UndoManager.cpp:322
UndoManager::Redo
void Redo(const Consumer &consumer)
Definition: UndoManager.cpp:408
UndoManager.h
UndoPush
UndoPush
Definition: UndoManager.h:122
UndoManager::GetLongDescription
wxLongLong_t GetLongDescription(unsigned int n, TranslatableString *desc, TranslatableString *size)
Definition: UndoManager.cpp:131
TransactionScope
RAII for a database transaction, possibly nested.
Definition: TransactionScope.h:28
UndoManager::ClearStates
void ClearStates()
Definition: UndoManager.cpp:249
min
int min(int a, int b)
Definition: CompareAudioCommand.cpp:106
Clipboard::Get
static Clipboard & Get()
Definition: Clipboard.cpp:29
UndoManager::current
int current
Definition: UndoManager.h:216
TrackId
An in-session identifier of track objects across undo states. It does not persist between sessions.
Definition: Track.h:150
TrackList::Get
static TrackList & Get(AudacityProject &project)
Definition: Track.cpp:377
UndoManager::GetShortDescription
void GetShortDescription(unsigned int n, TranslatableString *desc)
Definition: UndoManager.cpp:144
UndoManager::Get
static UndoManager & Get(AudacityProject &project)
Definition: UndoManager.cpp:57
AudacityProject
The top-level handle to an Audacity project. It serves as a source of events that other objects can b...
Definition: Project.h:89
SampleBlock
Abstract class allows access to contents of a block of sound samples, serialization as XML,...
Definition: SampleBlock.h:44
UndoManager::mClipboardSpaceUsage
unsigned long long mClipboardSpaceUsage
Definition: UndoManager.h:224
Sequence.h
UndoManager::GetSavedState
int GetSavedState() const
Definition: UndoManager.cpp:473
UndoPush::CONSOLIDATE
@ CONSOLIDATE
TrackList::Create
static std::shared_ptr< TrackList > Create(AudacityProject *pOwner)
Definition: Track.cpp:393
UndoManager::~UndoManager
~UndoManager()
Definition: UndoManager.cpp:74
UndoManager::GetCurrentState
unsigned int GetCurrentState()
Definition: UndoManager.cpp:261
UndoManager::RenameState
void RenameState(int state, const TranslatableString &longDescription, const TranslatableString &shortDescription)
Definition: UndoManager.cpp:308
UndoManager::ModifyState
void ModifyState(const TrackList *l, const SelectedRegion &selectedRegion, const std::shared_ptr< Tags > &tags)
Definition: UndoManager.cpp:276
TranslatableString::Translation
wxString Translation() const
Definition: TranslatableString.h:79
wxDEFINE_EVENT
wxDEFINE_EVENT(EVT_UNDO_PUSHED, wxCommandEvent)
safenew
#define safenew
Definition: MemoryX.h:10
UndoManager
Maintain a non-persistent list of states of the project, to support undo and redo commands.
Definition: UndoManager.h:137
BlockSpaceUsageAccumulator
std::function< void(const SampleBlock &) > BlockSpaceUsageAccumulator(unsigned long long &total)
Definition: SampleBlock.h:96
UndoManager::UnsavedChanges
bool UnsavedChanges() const
Definition: UndoManager.cpp:463
UndoManager::RemoveStates
void RemoveStates(size_t begin, size_t end)
Definition: UndoManager.cpp:207
pdlgHideCancelButton
@ pdlgHideCancelButton
Definition: ProgressDialog.h:39
UndoManager::GetNumStates
unsigned int GetNumStates()
Definition: UndoManager.cpp:256
UndoManager::space
SpaceArray space
Definition: UndoManager.h:223
SelectedRegion
Defines a selected portion of a project.
Definition: SelectedRegion.h:38
UndoManager::StateSaved
void StateSaved()
Definition: UndoManager.cpp:468