Audacity 3.2.0
HistoryWindow.cpp
Go to the documentation of this file.
1/**********************************************************************
2
3 Audacity: A Digital Audio Editor
4
5 HistoryWindow.cpp
6
7 Joshua Haberman
8 Leland Lucius
9
10*******************************************************************//*******************************************************************/
18
19
20#include "HistoryWindow.h"
21
22#include <wx/defs.h>
23#include <wx/button.h>
24#include <wx/frame.h>
25#include <wx/imaglist.h>
26#include <wx/listctrl.h>
27#include <wx/settings.h>
28#include <wx/spinctrl.h>
29#include <wx/stattext.h>
30#include <wx/textctrl.h>
31
32#include "AudioIO.h"
33#include "Clipboard.h"
34#include "CommonCommandFlags.h"
35#include "Diags.h"
36#include "../images/Arrow.xpm"
37#include "../images/Empty9x16.xpm"
38#include "UndoManager.h"
39#include "Project.h"
40#include "ProjectFileIO.h"
41#include "ProjectHistory.h"
42#include "ProjectWindows.h"
43#include "ShuttleGui.h"
44#include "AudacityMessageBox.h"
45#include "HelpSystem.h"
46
47#include <unordered_set>
48#include "SampleBlock.h"
49#include "WaveTrack.h"
50
51namespace {
53 using Type = unsigned long long;
54 using SpaceArray = std::vector<Type> ;
55
57 {
58 Type result = 0;
59 //TIMER_START( "CalculateSpaceUsage", space_calc );
61 tracks,
63 &seen
64 );
65 return result;
66 }
67
70
72 {
74
75 // After copies and pastes, a block file may be used in more than
76 // one place in one undo history state, and it may be used in more than
77 // one undo history state. It might even be used in two states, but not
78 // in another state that is between them -- as when you have state A,
79 // then make a cut to get state B, but then paste it back into state C.
80
81 // So be sure to count each block file once only, in the last undo item that
82 // contains it.
83
84 // Why the last and not the first? Because the user of the History dialog
85 // may DELETE undo states, oldest first. To reclaim disk space you must
86 // DELETE all states containing the block file. So the block file's
87 // contribution to space usage should be counted only in that latest
88 // state.
89
90 manager.VisitStates(
91 [this, &seen](const UndoStackElem &elem) {
92 // Scan all tracks at current level
93 if (auto pTracks = TrackList::FindUndoTracks(elem))
94 space.push_back(CalculateUsage(*pTracks, seen));
95 },
96 true // newest state first
97 );
98
99 // Count the usage of the clipboard separately, using another set. Do not
100 // multiple-count any block occurring multiple times within the clipboard.
101 seen.clear();
102 clipboardSpaceUsage = CalculateUsage(
103 Clipboard::Get().GetTracks(), seen);
104
105 //TIMER_STOP( space_calc );
106 }
107};
108}
109
110enum {
111 ID_AVAIL = 1000,
119
120BEGIN_EVENT_TABLE(HistoryDialog, wxDialogWrapper)
121 EVT_SHOW(HistoryDialog::OnShow)
122 EVT_SIZE(HistoryDialog::OnSize)
130
131#define HistoryTitle XO("History")
132
134 wxDialogWrapper(FindProjectFrame( parent ), wxID_ANY, HistoryTitle,
135 wxDefaultPosition, wxDefaultSize,
136 wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER )
137{
138 SetName();
139
141 mProject = parent;
142 mSelected = 0;
143 mAudioIOBusy = false;
144
145 //------------------------- Main section --------------------
146 // Construct the GUI.
147 ShuttleGui S(this, eIsCreating);
148 Populate(S);
149
151
154
155 if (parent)
158}
159
161{
162 auto imageList = std::make_unique<wxImageList>(9, 16);
163 imageList->Add(wxIcon(empty9x16_xpm));
164 imageList->Add(wxIcon(arrow_xpm));
165
166 S.SetBorder(5);
167 S.StartVerticalLay(true);
168 {
169 S.StartStatic(XO("&Manage History"), 1);
170 {
171 mList = S
172 .MinSize()
173 .ConnectRoot(wxEVT_KEY_DOWN, &HistoryDialog::OnListKeyDown)
174 .AddListControlReportMode(
175 { { XO("Action"), wxLIST_FORMAT_LEFT, 260 },
176 { XO("Used Space"), wxLIST_FORMAT_LEFT, 125 } },
177 wxLC_SINGLE_SEL
178 );
179
180 //Assign rather than set the image list, so that it is deleted later.
181 // AssignImageList takes ownership
182 mList->AssignImageList(imageList.release(), wxIMAGE_LIST_SMALL);
183
184 S.StartMultiColumn(3, wxCENTRE);
185 {
186 S.AddPrompt(XXO("&Total space used"));
187 mTotal = S.Id(ID_TOTAL).Style(wxTE_READONLY).AddTextBox({}, wxT(""), 10);
188 S.AddVariableText( {} )->Hide();
189
190#if defined(ALLOW_DISCARD)
191 S.AddPrompt(XXO("&Undo levels available"));
192 mAvail = S.Id(ID_AVAIL).Style(wxTE_READONLY).AddTextBox({}, wxT(""), 10);
193 S.AddVariableText( {} )->Hide();
194
195 S.AddPrompt(XXO("&Levels to discard"));
196 mLevels = safenew wxSpinCtrl(S.GetParent(),
197 ID_LEVELS,
198 wxT("1"),
199 wxDefaultPosition,
200 wxDefaultSize,
201 wxSP_ARROW_KEYS,
202 0,
204 0);
205 S.AddWindow(mLevels);
206 /* i18n-hint: (verb)*/
207 mDiscard = S.Id(ID_DISCARD).AddButton(XXO("&Discard"));
208#endif
209 S.AddPrompt(XXO("Clip&board space used"));
210 mClipboard = S.Style(wxTE_READONLY).AddTextBox({}, wxT(""), 10);
211
212#if defined(ALLOW_DISCARD)
213 S.Id(ID_DISCARD_CLIPBOARD).AddButton(XXO("D&iscard"));
214#endif
215 }
216 S.EndMultiColumn();
217 }
218 S.EndStatic();
219#if defined(ALLOW_DISCARD)
220 mCompact = safenew wxButton(this, ID_COMPACT, _("&Compact"));
221 S.AddStandardButtons(eOkButton | eHelpButton, mCompact);
222#else
223 S.AddStandardButtons(eOkButton | eHelpButton);
224#endif
225 }
226 S.EndVerticalLay();
227 // ----------------------- End of main section --------------
228
229 Layout();
230 Fit();
231 SetMinSize(GetSize());
232 mList->SetColumnWidth(0, mList->GetClientSize().x - mList->GetColumnWidth(1));
233 mList->SetTextColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT));
234}
235
237{
238 if (evt.type == AudioIOEvent::MONITOR)
239 return;
240 mAudioIOBusy = evt.on;
241
242#if defined(ALLOW_DISCARD)
243 mDiscard->Enable(!mAudioIOBusy);
244 mCompact->Enable(!mAudioIOBusy);
245#endif
246}
247
249{
251}
252
254{
255 switch (message.type) {
261 break;
262 default:
263 return;
264 }
266}
267
269{
270 if(IsShown())
271 DoUpdate();
272}
273
274bool HistoryDialog::Show( bool show )
275{
276 if ( show && !IsShown())
277 DoUpdate();
278 return wxDialogWrapper::Show( show );
279}
280
282{
283 int i = 0;
284
285 SpaceUsageCalculator calculator;
286 calculator.Calculate( *mManager );
287
288 // point to size for oldest state
289 auto iter = calculator.space.rbegin();
290
291 mList->DeleteAllItems();
292
293 wxLongLong_t total = 0;
296 [&]( const UndoStackElem &elem ){
297 const auto space = *iter++;
298 total += space;
299 const auto size = Internat::FormatSize(space);
300 const auto &desc = elem.description;
301 mList->InsertItem(i, desc.Translation(), i == mSelected ? 1 : 0);
302 mList->SetItem(i, 1, size.Translation());
303 ++i;
304 },
305 false // oldest state first
306 );
307
308 mTotal->SetValue(Internat::FormatSize(total).Translation());
309
310 auto clipboardUsage = calculator.clipboardSpaceUsage;
311 mClipboard->SetValue(Internat::FormatSize(clipboardUsage).Translation());
312#if defined(ALLOW_DISCARD)
313 FindWindowById(ID_DISCARD_CLIPBOARD)->Enable(clipboardUsage > 0);
314#endif
315
316 mList->EnsureVisible(mSelected);
317
318 mList->SetItemState(mSelected,
319 wxLIST_STATE_FOCUSED | wxLIST_STATE_SELECTED,
320 wxLIST_STATE_FOCUSED | wxLIST_STATE_SELECTED);
321
322 UpdateLevels();
323}
324
326{
327#if defined(ALLOW_DISCARD)
328 wxWindow *focus;
329 int value = mLevels->GetValue();
330
331 if (value > mSelected) {
332 value = mSelected;
333 }
334
335 if (value == 0) {
336 value = 1;
337 }
338
339 mLevels->SetValue(value);
340 mLevels->SetRange(1, mSelected);
341
342 mAvail->SetValue(wxString::Format(wxT("%d"), mSelected));
343
344 focus = FindFocus();
345 if ((focus == mDiscard || focus == mLevels) && mSelected == 0) {
346 mList->SetFocus();
347 }
348
349 mLevels->Enable(mSelected > 0);
350 mDiscard->Enable(!mAudioIOBusy && mSelected > 0);
351#endif
352}
353
354void HistoryDialog::OnDiscard(wxCommandEvent & WXUNUSED(event))
355{
356 int i = mLevels->GetValue();
357
358 mSelected -= i;
359 mManager->RemoveStates(0, i);
361
362 while(--i >= 0)
363 mList->DeleteItem(i);
364
365 DoUpdate();
366}
367
368void HistoryDialog::OnDiscardClipboard(wxCommandEvent & WXUNUSED(event))
369{
371}
372
373void HistoryDialog::OnCompact(wxCommandEvent & WXUNUSED(event))
374{
375 auto &projectFileIO = ProjectFileIO::Get(*mProject);
376
377 projectFileIO.ReopenProject();
378
379 auto baseFile = wxFileName(projectFileIO.GetFileName());
380 auto walFile = wxFileName(projectFileIO.GetFileName() + wxT("-wal"));
381 auto before = baseFile.GetSize() + walFile.GetSize();
382
383 projectFileIO.Compact({}, true);
384
385 auto after = baseFile.GetSize() + walFile.GetSize();
386
388 XO("Compacting actually freed %s of disk space.")
389 .Format(Internat::FormatSize((before - after).GetValue())),
390 XO("History"));
391}
392
393void HistoryDialog::OnGetURL(wxCommandEvent & WXUNUSED(event))
394{
395 HelpSystem::ShowHelp(this, L"Undo,_Redo_and_History");
396}
397
398void HistoryDialog::OnItemSelected(wxListEvent &event)
399{
400 if (mAudioIOBusy) {
401 mList->SetItemState(mSelected,
402 wxLIST_STATE_FOCUSED | wxLIST_STATE_SELECTED,
403 wxLIST_STATE_FOCUSED | wxLIST_STATE_SELECTED);
404 return;
405 }
406
407 int selected = event.GetIndex();
408 int i;
409
410 for (i = 0; i < mList->GetItemCount(); i++) {
411 mList->SetItemImage(i, 0);
412 if (i > selected)
413 mList->SetItemTextColour(i, *wxLIGHT_GREY);
414 else
415 mList->SetItemTextColour(i, mList->GetTextColour());
416 }
417 mList->SetItemImage(selected, 1);
418
419 // Do not do a SetStateTo() if we're not actually changing the selected
420 // entry. Doing so can cause unnecessary delays upon initial load or while
421 // clicking the same entry over and over.
422 if (selected != mSelected) {
423 ProjectHistory::Get( *mProject ).SetStateTo(selected);
424 }
425 mSelected = selected;
426
427 UpdateLevels();
428}
429
430void HistoryDialog::OnListKeyDown(wxKeyEvent & event)
431{
432 switch (event.GetKeyCode())
433 {
434 case WXK_RETURN:
435 // Don't know why wxListCtrls prevent default dialog action,
436 // but they do, so handle it.
437 EmulateButtonClickIfPresent(GetAffirmativeId());
438 break;
439
440 default:
441 event.Skip();
442 break;
443 }
444}
445
446void HistoryDialog::OnCloseWindow(wxCloseEvent & WXUNUSED(event))
447{
448 this->Show(false);
449}
450
451void HistoryDialog::OnShow(wxShowEvent & event)
452{
453 if (event.IsShown())
454 {
455 mList->SetFocus();
456 }
457}
458
459void HistoryDialog::OnSize(wxSizeEvent & WXUNUSED(event))
460{
461 Layout();
462 mList->SetColumnWidth(0, mList->GetClientSize().x - mList->GetColumnWidth(1));
463 if (mList->GetItemCount() > 0)
464 mList->EnsureVisible(mSelected);
465}
466
467// PrefsListener implementation
469{
470 bool shown = IsShown();
471 if (shown) {
472 Show(false);
473 }
474
475 SetSizer(nullptr);
476 DestroyChildren();
477
479 ShuttleGui S(this, eIsCreating);
480 Populate(S);
481
482 if (shown) {
483 Show(true);
484 }
485}
486
487// Remaining code hooks this add-on into the application
488#include "CommandContext.h"
489#include "MenuRegistry.h"
490
491namespace {
492
493// History window attached to each project is built on demand by:
494AttachedWindows::RegisteredFactory sHistoryWindowKey{
495 []( AudacityProject &parent ) -> wxWeakRef< wxWindow > {
496 auto &undoManager = UndoManager::Get( parent );
497 return safenew HistoryDialog( &parent, &undoManager );
498 }
499};
500
501// Define our extra menu item that invokes that factory
502void OnHistory(const CommandContext &context)
503{
504 auto &project = context.project;
505
506 auto historyWindow = &GetAttachedWindows(project).Get(sHistoryWindowKey);
507 historyWindow->Show();
508 historyWindow->Raise();
509}
510
511// Register that menu item
512
513using namespace MenuRegistry;
515 // History window should be available either for UndoAvailableFlag
516 // or RedoAvailableFlag,
517 // but we can't make the AddItem flags and mask have both,
518 // because they'd both have to be true for the
519 // command to be enabled.
520 // If user has Undone the entire stack, RedoAvailableFlag is on
521 // but UndoAvailableFlag is off.
522 // If user has done things but not Undone anything,
523 // RedoAvailableFlag is off but UndoAvailableFlag is on.
524 // So in either of those cases,
525 // (AudioIONotBusyFlag | UndoAvailableFlag | RedoAvailableFlag) mask
526 // would fail.
527 // The only way to fix this in the current architecture
528 // is to hack in special cases for RedoAvailableFlag
529 // in AudacityProject::UpdateMenus() (ugly)
530 // and CommandManager::HandleCommandEntry() (*really* ugly --
531 // shouldn't know about particular command names and flags).
532 // Here's the hack that would be necessary in
533 // AudacityProject::UpdateMenus(), if somebody decides to do it:
534 // // Because EnableUsingFlags requires all the flag bits match the
535 // // corresponding mask bits,
536 // // "UndoHistory" specifies only
537 // // AudioIONotBusyFlag | UndoAvailableFlag, because that
538 // // covers the majority of cases where it should be enabled.
539 // // If history is not empty but we've Undone the whole stack,
540 // // we also want to enable,
541 // // to show the Redo's on stack.
542 // // "UndoHistory" might already be enabled,
543 // // but add this check for RedoAvailableFlag.
544 // if (flags & RedoAvailableFlag)
545 // GetCommandManager()->Enable(wxT("UndoHistory"), true);
546 // So for now, enable the command regardless of stack.
547 // It will just show empty sometimes.
548 // FOR REDESIGN,
549 // clearly there are some limitations with the flags/mask bitmaps.
550
551 /* i18n-hint: Clicking this menu item shows the various editing steps
552 that have been taken.*/
553 Command( wxT("UndoHistory"), XXO("&History"), OnHistory,
555 wxT("View/Windows")
556};
557
558}
wxT("CloseDown"))
int AudacityMessageBox(const TranslatableString &message, const TranslatableString &caption, long style, wxWindow *parent, int x, int y)
END_EVENT_TABLE()
const ReservedCommandFlag & AudioIONotBusyFlag()
EVT_BUTTON(wxID_NO, DependencyDialog::OnNo) EVT_BUTTON(wxID_YES
EVT_LIST_ITEM_SELECTED(CurvesListID, EqualizationCurvesDialog::OnListSelectionChange) EVT_LIST_ITEM_DESELECTED(CurvesListID
XO("Cut/Copy/Paste")
XXO("&Cut/Copy/Paste Toolbar")
#define HistoryTitle
@ ID_COMPACT
@ ID_TOTAL
@ ID_AVAIL
@ ID_DISCARD
@ ID_DISCARD_CLIPBOARD
@ ID_LEVELS
@ ID_FILESIZE
#define _(s)
Definition: Internat.h:73
#define safenew
Definition: MemoryX.h:10
wxFrame * FindProjectFrame(AudacityProject *project)
Get a pointer to the window associated with a project, or null if the given pointer is null,...
AUDACITY_DLL_API AttachedWindows & GetAttachedWindows(AudacityProject &project)
accessors for certain important windows associated with each project
static const AttachedProjectObjects::RegisteredFactory manager
std::function< void(const SampleBlock &) > BlockSpaceUsageAccumulator(unsigned long long &total)
Definition: SampleBlock.h:105
@ eIsCreating
Definition: ShuttleGui.h:37
@ eOkButton
Definition: ShuttleGui.h:599
@ eHelpButton
Definition: ShuttleGui.h:603
const auto tracks
const auto project
#define S(N)
Definition: ToChars.cpp:64
void InspectBlocks(const TrackList &tracks, BlockInspector inspector, SampleBlockIDSet *pIDs)
Definition: WaveTrack.cpp:4459
std::unordered_set< SampleBlockID > SampleBlockIDSet
Definition: WaveTrack.h:1231
The top-level handle to an Audacity project. It serves as a source of events that other objects can b...
Definition: Project.h:90
static AudioIO * Get()
Definition: AudioIO.cpp:126
Subclass & Get(const RegisteredFactory &key)
Get reference to an attachment, creating on demand if not present, down-cast it to Subclass.
Definition: ClientData.h:317
static Clipboard & Get()
Definition: Clipboard.cpp:28
void Clear()
Definition: Clipboard.cpp:40
CommandContext provides additional information to an 'Apply()' command. It provides the project,...
AudacityProject & project
Abstract base class used in importing a file.
static void ShowHelp(wxWindow *parent, const FilePath &localFileName, const URLString &remoteURL, bool bModal=false, bool alwaysDefaultBrowser=false)
Definition: HelpSystem.cpp:233
Works with UndoManager to allow user to see descriptions of and undo previous commands....
Definition: HistoryWindow.h:30
void OnListKeyDown(wxKeyEvent &event)
bool Show(bool show=true) override
void UpdateDisplayForClipboard(struct ClipboardChangeMessage)
wxTextCtrl * mTotal
Definition: HistoryWindow.h:69
void OnSize(wxSizeEvent &event)
AudacityProject * mProject
Definition: HistoryWindow.h:66
Observer::Subscription mClipboardSubscription
Definition: HistoryWindow.h:64
void OnAudioIO(AudioIOEvent)
void OnDiscard(wxCommandEvent &event)
Observer::Subscription mUndoSubscription
Definition: HistoryWindow.h:63
wxTextCtrl * mClipboard
Definition: HistoryWindow.h:70
void OnDiscardClipboard(wxCommandEvent &event)
void UpdateDisplay(struct UndoRedoMessage)
HistoryDialog(AudacityProject *parent, UndoManager *manager)
void UpdatePrefs() override
void OnCompact(wxCommandEvent &event)
wxButton * mCompact
Definition: HistoryWindow.h:74
wxButton * mDiscard
Definition: HistoryWindow.h:73
wxListCtrl * mList
Definition: HistoryWindow.h:68
void OnShow(wxShowEvent &event)
void OnGetURL(wxCommandEvent &event)
void OnCloseWindow(wxCloseEvent &event)
void OnItemSelected(wxListEvent &event)
void DoUpdateDisplay()
wxTextCtrl * mAvail
Definition: HistoryWindow.h:71
void Populate(ShuttleGui &S)
UndoManager * mManager
Definition: HistoryWindow.h:67
Observer::Subscription mAudioIOSubscription
Definition: HistoryWindow.h:62
wxSpinCtrl * mLevels
Definition: HistoryWindow.h:72
static TranslatableString FormatSize(wxLongLong size)
Convert a number to a string while formatting it in bytes, KB, MB, GB.
Definition: Internat.cpp:203
Subscription Subscribe(Callback callback)
Connect a callback to the Publisher; later-connected are called earlier.
Definition: Observer.h:199
static ProjectFileIO & Get(AudacityProject &project)
void SetStateTo(unsigned int n, bool doAutosave=true)
static ProjectHistory & Get(AudacityProject &project)
Generates classes whose instances register items at construction.
Definition: Registry.h:388
Derived from ShuttleGuiBase, an Audacity specific class for shuttling data to and from GUI.
Definition: ShuttleGui.h:630
A flat linked list of tracks supporting Add, Remove, Clear, and Contains, serialization of the list o...
Definition: Track.h:975
static TrackList * FindUndoTracks(const UndoStackElem &state)
Definition: Track.cpp:1406
wxString Translation() const
Maintain a non-persistent list of states of the project, to support undo and redo commands.
Definition: UndoManager.h:155
void VisitStates(const Consumer &consumer, bool newestFirst)
Give read-only access to all states.
void RemoveStates(size_t begin, size_t end)
static UndoManager & Get(AudacityProject &project)
Definition: UndoManager.cpp:71
unsigned int GetCurrentState()
void SetTitle(const TranslatableString &title)
std::unique_ptr< WindowPlacement > FindFocus()
Find the window that is accepting keyboard input, if any.
Definition: BasicUI.h:373
ExportResult Show(ExportTask exportTask)
constexpr auto Command
Definition: MenuRegistry.h:456
const TranslatableString desc
Definition: ExportPCM.cpp:51
void OnHistory(const CommandContext &context)
AttachedWindows::RegisteredFactory sHistoryWindowKey
bool on
Definition: AudioIO.h:66
enum AudioIOEvent::Type type
Message is sent during idle time by the global clipboard.
Definition: Clipboard.h:24
Type of message published by UndoManager.
Definition: UndoManager.h:55
@ Purge
Undo or redo states eliminated.
Definition: UndoManager.h:69
enum UndoRedoMessage::Type type
Holds one item with description and time range for the UndoManager.
Definition: UndoManager.h:117
TranslatableString description
Definition: UndoManager.h:129
Type CalculateUsage(const TrackList &tracks, SampleBlockIDSet &seen)