Audacity  3.0.3
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 "DBConnection.h"
30 #include "Diags.h"
31 #include "Project.h"
32 #include "SampleBlock.h"
33 #include "Sequence.h"
34 #include "WaveTrack.h" // temp
35 //#include "NoteTrack.h" // for Sonify* function declarations
36 #include "Diags.h"
37 #include "Tags.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 
139  *size = Internat::FormatSize(space[n]);
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
226  auto pConnection = ConnectionPtr::Get(mProject).mpConnection.get();
227  if (pConnection)
228  pTrans.emplace(*pConnection, "DiscardingUndoStates");
229 
230  for (size_t ii = begin; ii < end; ++ii) {
231  RemoveStateAt(begin);
232 
233  if (current > begin)
234  --current;
235  if (saved > static_cast<int>(begin))
236  --saved;
237  }
238 
239  // Success, commit the savepoint
240  if (pTrans)
241  pTrans->Commit();
242 
243  if (begin != end)
244  // wxWidgets will own the event object
245  mProject.QueueEvent( safenew wxCommandEvent{ EVT_UNDO_PURGE } );
246 
247  // Check sanity
248  wxASSERT_MSG(
249  nDeleted == 0 || // maybe bypassing all deletions
250  nDeleted == nToDelete, "Block count was misestimated");
251 }
252 
254 {
255  RemoveStates(0, stack.size());
256  current = -1;
257  saved = -1;
258 }
259 
261 {
262  return stack.size();
263 }
264 
266 {
267  return current;
268 }
269 
271 {
272  return (current > 0);
273 }
274 
276 {
277  return (current < (int)stack.size() - 1);
278 }
279 
281  const SelectedRegion &selectedRegion,
282  const std::shared_ptr<Tags> &tags)
283 {
284  if (current == wxNOT_FOUND) {
285  return;
286  }
287 
288 // SonifyBeginModifyState();
289  // Delete current -- not necessary, but let's reclaim space early
290  stack[current]->state.tracks.reset();
291 
292  // Duplicate
293  auto tracksCopy = TrackList::Create( nullptr );
294  for (auto t : *l) {
295  if ( t->GetId() == TrackId{} )
296  // Don't copy a pending added track
297  continue;
298  tracksCopy->Add(t->Duplicate());
299  }
300 
301  // Replace
302  stack[current]->state.tracks = std::move(tracksCopy);
303  stack[current]->state.tags = tags;
304 
305  stack[current]->state.selectedRegion = selectedRegion;
306 // SonifyEndModifyState();
307 
308  // wxWidgets will own the event object
309  mProject.QueueEvent( safenew wxCommandEvent{ EVT_UNDO_MODIFIED } );
310 }
311 
312 void UndoManager::RenameState( int state,
313  const TranslatableString &longDescription,
314  const TranslatableString &shortDescription)
315 {
316  if (state >= 0 && state < stack.size() ) {
317  auto &theState = *stack[state];
318  theState.description = longDescription;
319  theState.shortDescription = shortDescription;
320 
321  // wxWidgets will own the event object
322  mProject.QueueEvent( safenew wxCommandEvent{ EVT_UNDO_RENAMED } );
323  }
324 }
325 
327  const SelectedRegion &selectedRegion,
328  const std::shared_ptr<Tags> &tags,
329  const TranslatableString &longDescription,
330  const TranslatableString &shortDescription,
331  UndoPush flags)
332 {
333  if ( (flags & UndoPush::CONSOLIDATE) != UndoPush::NONE &&
334  // compare full translations not msgids!
335  lastAction.Translation() == longDescription.Translation() &&
336  mayConsolidate ) {
337  ModifyState(l, selectedRegion, tags);
338  // MB: If the "saved" state was modified by ModifyState, reset
339  // it so that UnsavedChanges returns true.
340  if (current == saved) {
341  saved = -1;
342  }
343  return;
344  }
345 
346  auto tracksCopy = TrackList::Create( nullptr );
347  for (auto t : *l) {
348  if ( t->GetId() == TrackId{} )
349  // Don't copy a pending added track
350  continue;
351  tracksCopy->Add(t->Duplicate());
352  }
353 
354  mayConsolidate = true;
355 
356  AbandonRedo();
357 
358  // Assume tags was duplicated before any changes.
359  // Just save a NEW shared_ptr to it.
360  stack.push_back(
361  std::make_unique<UndoStackElem>
362  (std::move(tracksCopy),
363  longDescription, shortDescription, selectedRegion, tags)
364  );
365 
366  current++;
367 
368  lastAction = longDescription;
369 
370  // wxWidgets will own the event object
371  mProject.QueueEvent( safenew wxCommandEvent{ EVT_UNDO_PUSHED } );
372 }
373 
375 {
376  if (saved > current) {
377  saved = -1;
378  }
379  RemoveStates( current + 1, stack.size() );
380 }
381 
382 void UndoManager::SetStateTo(unsigned int n, const Consumer &consumer)
383 {
384  wxASSERT(n < stack.size());
385 
386  current = n;
387 
388  lastAction = {};
389  mayConsolidate = false;
390 
391  consumer( *stack[current] );
392 
393  // wxWidgets will own the event object
394  mProject.QueueEvent( safenew wxCommandEvent{ EVT_UNDO_RESET } );
395 }
396 
397 void UndoManager::Undo(const Consumer &consumer)
398 {
399  wxASSERT(UndoAvailable());
400 
401  current--;
402 
403  lastAction = {};
404  mayConsolidate = false;
405 
406  consumer( *stack[current] );
407 
408  // wxWidgets will own the event object
409  mProject.QueueEvent( safenew wxCommandEvent{ EVT_UNDO_OR_REDO } );
410 }
411 
412 void UndoManager::Redo(const Consumer &consumer)
413 {
414  wxASSERT(RedoAvailable());
415 
416  current++;
417 
418  /*
419  if (!RedoAvailable()) {
420  *sel0 = stack[current]->sel0;
421  *sel1 = stack[current]->sel1;
422  }
423  else {
424  current++;
425  *sel0 = stack[current]->sel0;
426  *sel1 = stack[current]->sel1;
427  current--;
428  }
429  */
430 
431  lastAction = {};
432  mayConsolidate = false;
433 
434  consumer( *stack[current] );
435 
436  // wxWidgets will own the event object
437  mProject.QueueEvent( safenew wxCommandEvent{ EVT_UNDO_OR_REDO } );
438 }
439 
440 void UndoManager::VisitStates( const Consumer &consumer, bool newestFirst )
441 {
442  auto fn = [&]( decltype(stack[0]) &ptr ){ consumer( *ptr ); };
443  if (newestFirst)
444  std::for_each(stack.rbegin(), stack.rend(), fn);
445  else
446  std::for_each(stack.begin(), stack.end(), fn);
447 }
448 
450  const Consumer &consumer, size_t begin, size_t end )
451 {
452  auto size = stack.size();
453  if (begin < end) {
454  end = std::min(end, size);
455  for (auto ii = begin; ii < end; ++ii)
456  consumer(*stack[ii]);
457  }
458  else {
459  if (size == 0)
460  return;
461  begin = std::min(begin, size - 1);
462  for (auto ii = begin; ii > end; --ii)
463  consumer(*stack[ii]);
464  }
465 }
466 
468 {
469  return (saved != current);
470 }
471 
473 {
474  saved = current;
475 }
476 
478 {
479  return saved;
480 }
481 
482 // currently unused
483 //void UndoManager::Debug()
484 //{
485 // for (unsigned int i = 0; i < stack.size(); i++) {
486 // for (auto t : stack[i]->tracks->Any())
487 // wxPrintf(wxT("*%d* %s %f\n"),
488 // i, (i == (unsigned int)current) ? wxT("-->") : wxT(" "),
489 // t ? t->GetEndTime()-t->GetStartTime() : 0);
490 // }
491 //}
492 
WaveTrack.h
Optional::emplace
X & emplace(Args &&... args)
Definition: MemoryX.h:193
TranslatableString
Holds a msgid for the translation catalog; may also bind format arguments.
Definition: TranslatableString.h:32
WaveTrackFactory::Get
static WaveTrackFactory & Get(AudacityProject &project)
Definition: WaveTrack.cpp:2901
SampleBlock::GetBlockID
virtual SampleBlockID GetBlockID() const =0
SampleBlockIDSet
std::unordered_set< SampleBlockID > SampleBlockIDSet
Definition: WaveTrack.h:690
Optional
Like a smart pointer, allows for object to not exist (nullptr)
Definition: MemoryX.h:144
UndoManager::VisitStates
void VisitStates(const Consumer &consumer, bool newestFirst)
Give read-only access to all states.
Definition: UndoManager.cpp:440
fn
static const auto fn
Definition: WaveformView.cpp:1113
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:397
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:1280
UndoManager::RedoAvailable
bool RedoAvailable()
Definition: UndoManager.cpp:275
UndoPush::NONE
@ NONE
XO
#define XO(s)
Definition: Internat.h:31
UndoManager::UndoAvailable
bool UndoAvailable()
Definition: UndoManager.cpp:270
ProgressDialog.h
UndoManager::UndoManager
UndoManager(AudacityProject &project)
Definition: UndoManager.cpp:67
DBConnection.h
Declare DBConnection, which maintains database connection and associated status and background thread...
InspectBlocks
void InspectBlocks(const TrackList &tracks, BlockInspector inspector, SampleBlockIDSet *pIDs)
Definition: WaveTrack.cpp:2882
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:39
UndoManager::AbandonRedo
void AbandonRedo()
Definition: UndoManager.cpp:374
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:382
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:203
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:326
UndoManager::Redo
void Redo(const Consumer &consumer)
Definition: UndoManager.cpp:412
UndoManager.h
UndoPush
UndoPush
Definition: UndoManager.h:122
UndoManager::GetLongDescription
wxLongLong_t GetLongDescription(unsigned int n, TranslatableString *desc, TranslatableString *size)
Definition: UndoManager.cpp:131
UndoManager::ClearStates
void ClearStates()
Definition: UndoManager.cpp:253
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:165
TrackList::Get
static TrackList & Get(AudacityProject &project)
Definition: Track.cpp:506
UndoManager::GetShortDescription
void GetShortDescription(unsigned int n, TranslatableString *desc)
Definition: UndoManager.cpp:144
ConnectionPtr::Get
static ConnectionPtr & Get(AudacityProject &project)
Definition: DBConnection.cpp:693
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:92
SampleBlock
Abstract class allows access to contents of a block of sound samples, serialization as XML,...
Definition: SampleBlock.h:45
UndoManager::mClipboardSpaceUsage
unsigned long long mClipboardSpaceUsage
Definition: UndoManager.h:224
Sequence.h
UndoManager::GetSavedState
int GetSavedState() const
Definition: UndoManager.cpp:477
UndoPush::CONSOLIDATE
@ CONSOLIDATE
TrackList::Create
static std::shared_ptr< TrackList > Create(AudacityProject *pOwner)
Definition: Track.cpp:523
UndoManager::~UndoManager
~UndoManager()
Definition: UndoManager.cpp:74
ConnectionPtr::mpConnection
Connection mpConnection
Definition: DBConnection.h:169
UndoManager::GetCurrentState
unsigned int GetCurrentState()
Definition: UndoManager.cpp:265
UndoManager::RenameState
void RenameState(int state, const TranslatableString &longDescription, const TranslatableString &shortDescription)
Definition: UndoManager.cpp:312
UndoManager::ModifyState
void ModifyState(const TrackList *l, const SelectedRegion &selectedRegion, const std::shared_ptr< Tags > &tags)
Definition: UndoManager.cpp:280
TranslatableString::Translation
wxString Translation() const
Definition: TranslatableString.h:79
wxDEFINE_EVENT
wxDEFINE_EVENT(EVT_UNDO_PUSHED, wxCommandEvent)
TransactionScope::Commit
bool Commit()
Definition: DBConnection.cpp:658
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:97
UndoManager::UnsavedChanges
bool UnsavedChanges() const
Definition: UndoManager.cpp:467
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:260
UndoManager::space
SpaceArray space
Definition: UndoManager.h:223
SelectedRegion
Defines a selected portion of a project.
Definition: SelectedRegion.h:35
UndoManager::StateSaved
void StateSaved()
Definition: UndoManager.cpp:472