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