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 "UndoTracks.h"
40#include "Project.h"
41#include "ProjectFileIO.h"
42#include "ProjectHistory.h"
43#include "ProjectWindows.h"
44#include "ShuttleGui.h"
45#include "AudacityMessageBox.h"
46#include "HelpSystem.h"
47
48#include <unordered_set>
49#include "SampleBlock.h"
50#include "WaveTrack.h"
51#include "WaveTrackUtilities.h"
52
53namespace {
54using namespace WaveTrackUtilities;
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 if (auto pTracks = UndoTracks::Find(elem))
97 space.push_back(CalculateUsage(*pTracks, 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
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{
254}
255
257{
258 switch (message.type) {
264 break;
265 default:
266 return;
267 }
269}
270
272{
273 if(IsShown())
274 DoUpdate();
275}
276
277bool HistoryDialog::Show( bool show )
278{
279 if ( show && !IsShown())
280 DoUpdate();
281 return wxDialogWrapper::Show( show );
282}
283
285{
286 int i = 0;
287
288 SpaceUsageCalculator calculator;
289 calculator.Calculate( *mManager );
290
291 // point to size for oldest state
292 auto iter = calculator.space.rbegin();
293
294 mList->DeleteAllItems();
295
296 wxLongLong_t total = 0;
299 [&]( const UndoStackElem &elem ){
300 const auto space = *iter++;
301 total += space;
302 const auto size = Internat::FormatSize(space);
303 const auto &desc = elem.description;
304 mList->InsertItem(i, desc.Translation(), i == mSelected ? 1 : 0);
305 mList->SetItem(i, 1, size.Translation());
306 ++i;
307 },
308 false // oldest state first
309 );
310
311 mTotal->SetValue(Internat::FormatSize(total).Translation());
312
313 auto clipboardUsage = calculator.clipboardSpaceUsage;
314 mClipboard->SetValue(Internat::FormatSize(clipboardUsage).Translation());
315#if defined(ALLOW_DISCARD)
316 FindWindowById(ID_DISCARD_CLIPBOARD)->Enable(clipboardUsage > 0);
317#endif
318
319 mList->EnsureVisible(mSelected);
320
321 mList->SetItemState(mSelected,
322 wxLIST_STATE_FOCUSED | wxLIST_STATE_SELECTED,
323 wxLIST_STATE_FOCUSED | wxLIST_STATE_SELECTED);
324
325 UpdateLevels();
326}
327
329{
330#if defined(ALLOW_DISCARD)
331 wxWindow *focus;
332 int value = mLevels->GetValue();
333
334 if (value > mSelected) {
335 value = mSelected;
336 }
337
338 if (value == 0) {
339 value = 1;
340 }
341
342 mLevels->SetValue(value);
343 mLevels->SetRange(1, mSelected);
344
345 mAvail->SetValue(wxString::Format(wxT("%d"), mSelected));
346
347 focus = FindFocus();
348 if ((focus == mDiscard || focus == mLevels) && mSelected == 0) {
349 mList->SetFocus();
350 }
351
352 mLevels->Enable(mSelected > 0);
353 mDiscard->Enable(!mAudioIOBusy && mSelected > 0);
354#endif
355}
356
357void HistoryDialog::OnDiscard(wxCommandEvent & WXUNUSED(event))
358{
359 int i = mLevels->GetValue();
360
361 mSelected -= i;
362 mManager->RemoveStates(0, i);
364
365 while(--i >= 0)
366 mList->DeleteItem(i);
367
368 DoUpdate();
369}
370
371void HistoryDialog::OnDiscardClipboard(wxCommandEvent & WXUNUSED(event))
372{
374}
375
376void HistoryDialog::OnCompact(wxCommandEvent & WXUNUSED(event))
377{
378 auto &projectFileIO = ProjectFileIO::Get(*mProject);
379
380 projectFileIO.ReopenProject();
381
382 auto baseFile = wxFileName(projectFileIO.GetFileName());
383 auto walFile = wxFileName(projectFileIO.GetFileName() + wxT("-wal"));
384 auto before = baseFile.GetSize() + walFile.GetSize();
385
386 projectFileIO.Compact({}, true);
387
388 auto after = baseFile.GetSize() + walFile.GetSize();
389
391 XO("Compacting actually freed %s of disk space.")
392 .Format(Internat::FormatSize((before - after).GetValue())),
393 XO("History"));
394}
395
396void HistoryDialog::OnGetURL(wxCommandEvent & WXUNUSED(event))
397{
398 HelpSystem::ShowHelp(this, L"Undo,_Redo_and_History");
399}
400
401void HistoryDialog::OnItemSelected(wxListEvent &event)
402{
403 if (mAudioIOBusy) {
404 mList->SetItemState(mSelected,
405 wxLIST_STATE_FOCUSED | wxLIST_STATE_SELECTED,
406 wxLIST_STATE_FOCUSED | wxLIST_STATE_SELECTED);
407 return;
408 }
409
410 int selected = event.GetIndex();
411 int i;
412
413 for (i = 0; i < mList->GetItemCount(); i++) {
414 mList->SetItemImage(i, 0);
415 if (i > selected)
416 mList->SetItemTextColour(i, *wxLIGHT_GREY);
417 else
418 mList->SetItemTextColour(i, mList->GetTextColour());
419 }
420 mList->SetItemImage(selected, 1);
421
422 // Do not do a SetStateTo() if we're not actually changing the selected
423 // entry. Doing so can cause unnecessary delays upon initial load or while
424 // clicking the same entry over and over.
425 if (selected != mSelected) {
426 ProjectHistory::Get( *mProject ).SetStateTo(selected);
427 }
428 mSelected = selected;
429
430 UpdateLevels();
431}
432
433void HistoryDialog::OnListKeyDown(wxKeyEvent & event)
434{
435 switch (event.GetKeyCode())
436 {
437 case WXK_RETURN:
438 // Don't know why wxListCtrls prevent default dialog action,
439 // but they do, so handle it.
440 EmulateButtonClickIfPresent(GetAffirmativeId());
441 break;
442
443 default:
444 event.Skip();
445 break;
446 }
447}
448
449void HistoryDialog::OnCloseWindow(wxCloseEvent & WXUNUSED(event))
450{
451 this->Show(false);
452}
453
454void HistoryDialog::OnShow(wxShowEvent & event)
455{
456 if (event.IsShown())
457 {
458 mList->SetFocus();
459 }
460}
461
462void HistoryDialog::OnSize(wxSizeEvent & WXUNUSED(event))
463{
464 Layout();
465 mList->SetColumnWidth(0, mList->GetClientSize().x - mList->GetColumnWidth(1));
466 if (mList->GetItemCount() > 0)
467 mList->EnsureVisible(mSelected);
468}
469
470// PrefsListener implementation
472{
473 bool shown = IsShown();
474 if (shown) {
475 Show(false);
476 }
477
478 SetSizer(nullptr);
479 DestroyChildren();
480
482 ShuttleGui S(this, eIsCreating);
483 Populate(S);
484
485 if (shown) {
486 Show(true);
487 }
488}
489
490// Remaining code hooks this add-on into the application
491#include "CommandContext.h"
492#include "MenuRegistry.h"
493
494namespace {
495
496// History window attached to each project is built on demand by:
497AttachedWindows::RegisteredFactory sHistoryWindowKey{
498 []( AudacityProject &parent ) -> wxWeakRef< wxWindow > {
499 auto &undoManager = UndoManager::Get( parent );
500 return safenew HistoryDialog( &parent, &undoManager );
501 }
502};
503
504// Define our extra menu item that invokes that factory
505void OnHistory(const CommandContext &context)
506{
507 auto &project = context.project;
508
509 auto historyWindow = &GetAttachedWindows(project).Get(sHistoryWindowKey);
510 historyWindow->Show();
511 historyWindow->Raise();
512}
513
514// Register that menu item
515
516using namespace MenuRegistry;
518 // History window should be available either for UndoAvailableFlag
519 // or RedoAvailableFlag,
520 // but we can't make the AddItem flags and mask have both,
521 // because they'd both have to be true for the
522 // command to be enabled.
523 // If user has Undone the entire stack, RedoAvailableFlag is on
524 // but UndoAvailableFlag is off.
525 // If user has done things but not Undone anything,
526 // RedoAvailableFlag is off but UndoAvailableFlag is on.
527 // So in either of those cases,
528 // (AudioIONotBusyFlag | UndoAvailableFlag | RedoAvailableFlag) mask
529 // would fail.
530 // The only way to fix this in the current architecture
531 // is to hack in special cases for RedoAvailableFlag
532 // in AudacityProject::UpdateMenus() (ugly)
533 // and CommandManager::HandleCommandEntry() (*really* ugly --
534 // shouldn't know about particular command names and flags).
535 // Here's the hack that would be necessary in
536 // AudacityProject::UpdateMenus(), if somebody decides to do it:
537 // // Because EnableUsingFlags requires all the flag bits match the
538 // // corresponding mask bits,
539 // // "UndoHistory" specifies only
540 // // AudioIONotBusyFlag | UndoAvailableFlag, because that
541 // // covers the majority of cases where it should be enabled.
542 // // If history is not empty but we've Undone the whole stack,
543 // // we also want to enable,
544 // // to show the Redo's on stack.
545 // // "UndoHistory" might already be enabled,
546 // // but add this check for RedoAvailableFlag.
547 // if (flags & RedoAvailableFlag)
548 // GetCommandManager()->Enable(wxT("UndoHistory"), true);
549 // So for now, enable the command regardless of stack.
550 // It will just show empty sometimes.
551 // FOR REDESIGN,
552 // clearly there are some limitations with the flags/mask bitmaps.
553
554 /* i18n-hint: Clicking this menu item shows the various editing steps
555 that have been taken.*/
556 Command( wxT("UndoHistory"), XXO("&History"), OnHistory,
558 wxT("View/Windows")
559};
560
561}
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:9
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(SampleBlockConstPtr) > BlockSpaceUsageAccumulator(unsigned long long &total)
Definition: SampleBlock.h:108
@ eIsCreating
Definition: ShuttleGui.h:37
@ eOkButton
Definition: ShuttleGui.h:609
@ eHelpButton
Definition: ShuttleGui.h:613
const auto tracks
const auto project
#define S(N)
Definition: ToChars.cpp:64
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:318
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:231
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:179
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:640
A flat linked list of tracks supporting Add, Remove, Clear, and Contains, serialization of the list o...
Definition: Track.h:850
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:375
IMPORT_EXPORT_API ExportResult Show(ExportTask exportTask)
constexpr auto Command
Definition: MenuRegistry.h:456
TRACK_API TrackList * Find(const UndoStackElem &state)
Definition: UndoTracks.cpp:47
WAVE_TRACK_API void InspectBlocks(const TrackList &tracks, BlockInspector inspector, SampleBlockIDSet *pIDs=nullptr)
std::unordered_set< SampleBlockID > SampleBlockIDSet
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)