mirror of
https://github.com/cookiengineer/audacity
synced 2025-04-30 15:49:41 +02:00
List of commands that were executed in the `src directory`: * sed -i 's/Audacity: A Digital Audio Editor/Tenacity/g' *.h * sed -i 's/Audacity: A Digital Audio Editor/Tenacity/g' *.cpp Signed-off-by: Panagiotis Vasilopoulos <hello@alwayslivid.com>
509 lines
12 KiB
C++
509 lines
12 KiB
C++
/**********************************************************************
|
|
|
|
Tenacity
|
|
|
|
AutoRecoveryDialog.cpp
|
|
|
|
Paul Licameli split from AutoRecovery.cpp
|
|
|
|
**********************************************************************/
|
|
|
|
#include "AutoRecoveryDialog.h"
|
|
|
|
#include "ActiveProjects.h"
|
|
#include "ProjectManager.h"
|
|
#include "ProjectFileIO.h"
|
|
#include "ProjectFileManager.h"
|
|
#include "ShuttleGui.h"
|
|
#include "TempDirectory.h"
|
|
#include "widgets/AudacityMessageBox.h"
|
|
#include "widgets/wxPanelWrapper.h"
|
|
|
|
#include <wx/dir.h>
|
|
#include <wx/evtloop.h>
|
|
#include <wx/filefn.h>
|
|
#include <wx/filename.h>
|
|
#include <wx/listctrl.h>
|
|
|
|
enum {
|
|
ID_QUIT_AUDACITY = 10000,
|
|
ID_DISCARD_SELECTED,
|
|
ID_RECOVER_SELECTED,
|
|
ID_SKIP,
|
|
ID_FILE_LIST
|
|
};
|
|
|
|
class AutoRecoveryDialog final : public wxDialogWrapper
|
|
{
|
|
public:
|
|
explicit AutoRecoveryDialog(AudacityProject *proj);
|
|
|
|
bool HasRecoverables() const;
|
|
FilePaths GetRecoverables();
|
|
|
|
private:
|
|
void PopulateOrExchange(ShuttleGui &S);
|
|
void PopulateList();
|
|
bool HaveChecked();
|
|
|
|
void OnQuitAudacity(wxCommandEvent &evt);
|
|
void OnDiscardSelected(wxCommandEvent &evt);
|
|
void OnRecoverSelected(wxCommandEvent &evt);
|
|
void OnSkip(wxCommandEvent &evt);
|
|
void OnColumnClicked(wxListEvent &evt);
|
|
void OnItemActivated(wxListEvent &evt);
|
|
void OnListKeyDown(wxKeyEvent &evt);
|
|
|
|
FilePaths mFiles;
|
|
wxListCtrl *mFileList;
|
|
AudacityProject *mProject;
|
|
|
|
public:
|
|
DECLARE_EVENT_TABLE()
|
|
};
|
|
|
|
BEGIN_EVENT_TABLE(AutoRecoveryDialog, wxDialogWrapper)
|
|
EVT_BUTTON(ID_QUIT_AUDACITY, AutoRecoveryDialog::OnQuitAudacity)
|
|
EVT_BUTTON(ID_DISCARD_SELECTED, AutoRecoveryDialog::OnDiscardSelected)
|
|
EVT_BUTTON(ID_RECOVER_SELECTED, AutoRecoveryDialog::OnRecoverSelected)
|
|
EVT_BUTTON(ID_SKIP, AutoRecoveryDialog::OnSkip)
|
|
EVT_LIST_COL_CLICK(ID_FILE_LIST, AutoRecoveryDialog::OnColumnClicked)
|
|
EVT_LIST_ITEM_ACTIVATED(ID_FILE_LIST, AutoRecoveryDialog::OnItemActivated)
|
|
END_EVENT_TABLE()
|
|
|
|
AutoRecoveryDialog::AutoRecoveryDialog(AudacityProject *project)
|
|
: wxDialogWrapper(nullptr, wxID_ANY, XO("Automatic Crash Recovery"),
|
|
wxDefaultPosition, wxDefaultSize,
|
|
(wxDEFAULT_DIALOG_STYLE & (~wxCLOSE_BOX)) | wxRESIZE_BORDER), // no close box
|
|
mProject(project)
|
|
{
|
|
SetName();
|
|
ShuttleGui S(this, eIsCreating);
|
|
PopulateOrExchange(S);
|
|
}
|
|
|
|
bool AutoRecoveryDialog::HasRecoverables() const
|
|
{
|
|
return mFiles.size() > 0;
|
|
}
|
|
|
|
FilePaths AutoRecoveryDialog::GetRecoverables()
|
|
{
|
|
return mFiles;
|
|
}
|
|
|
|
void AutoRecoveryDialog::PopulateOrExchange(ShuttleGui &S)
|
|
{
|
|
S.SetBorder(5);
|
|
S.StartVerticalLay(wxEXPAND, 1);
|
|
{
|
|
S.AddFixedText(
|
|
XO("The following projects were not saved properly the last time Audacity was run and "
|
|
"can be automatically recovered.\n\n"
|
|
"After recovery, save the projects to ensure changes are written to disk."),
|
|
false,
|
|
500);
|
|
|
|
S.StartStatic(XO("Recoverable &projects"), 1);
|
|
{
|
|
mFileList = S.Id(ID_FILE_LIST)
|
|
.ConnectRoot(wxEVT_KEY_DOWN, &AutoRecoveryDialog::OnListKeyDown)
|
|
.AddListControlReportMode(
|
|
{
|
|
/*i18n-hint: (verb). It instruct the user to select items.*/
|
|
XO("Select"),
|
|
/*i18n-hint: (noun). It's the name of the project to recover.*/
|
|
XO("Name")
|
|
});
|
|
mFileList->EnableCheckBoxes();
|
|
PopulateList();
|
|
}
|
|
S.EndStatic();
|
|
|
|
S.StartHorizontalLay(wxALIGN_CENTRE, 0);
|
|
{
|
|
S.Id(ID_QUIT_AUDACITY).AddButton(XXO("&Quit Audacity"));
|
|
S.Id(ID_DISCARD_SELECTED).AddButton(XXO("&Discard Selected"));
|
|
S.Id(ID_RECOVER_SELECTED).AddButton(XXO("&Recover Selected"), wxALIGN_CENTRE, true);
|
|
S.Id(ID_SKIP).AddButton(XXO("&Skip"));
|
|
|
|
SetAffirmativeId(ID_RECOVER_SELECTED);
|
|
SetEscapeId(ID_SKIP);
|
|
}
|
|
S.EndHorizontalLay();
|
|
}
|
|
S.EndVerticalLay();
|
|
|
|
Layout();
|
|
Fit();
|
|
SetMinSize(GetSize());
|
|
|
|
// Sometimes it centers on wxGTK and sometimes it doesn't.
|
|
// Yielding before centering seems to be a good workaround,
|
|
// but will leave to implement on a rainy day.
|
|
Center();
|
|
}
|
|
|
|
void AutoRecoveryDialog::PopulateList()
|
|
{
|
|
wxString tempdir = TempDirectory::TempDir();
|
|
wxString pattern = wxT("*.") + FileNames::UnsavedProjectExtension();
|
|
FilePaths files;
|
|
|
|
wxDir::GetAllFiles(tempdir, &files, pattern, wxDIR_FILES);
|
|
|
|
FilePaths active = ActiveProjects::GetAll();
|
|
|
|
for (auto file : active)
|
|
{
|
|
wxFileName fn = file;
|
|
if (fn.FileExists())
|
|
{
|
|
FilePath fullPath = fn.GetFullPath();
|
|
if (files.Index(fullPath) == wxNOT_FOUND)
|
|
{
|
|
files.push_back(fullPath);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ActiveProjects::Remove(file);
|
|
}
|
|
}
|
|
|
|
FilePath activeFile;
|
|
if (mProject)
|
|
{
|
|
auto &projectFileIO = ProjectFileIO::Get(*mProject);
|
|
activeFile = projectFileIO.GetFileName();
|
|
}
|
|
|
|
mFiles.clear();
|
|
mFileList->DeleteAllItems();
|
|
long item = 0;
|
|
|
|
for (auto file : files)
|
|
{
|
|
wxFileName fn = file;
|
|
if (fn != activeFile)
|
|
{
|
|
mFiles.push_back(fn.GetFullPath());
|
|
mFileList->InsertItem(item, wxT(""));
|
|
mFileList->SetItem(item, 1, fn.GetName());
|
|
mFileList->CheckItem(item, true);
|
|
item++;
|
|
}
|
|
}
|
|
mFileList->SetMinSize(mFileList->GetBestSize());
|
|
mFileList->SetColumnWidth(0, wxLIST_AUTOSIZE_USEHEADER);
|
|
mFileList->SetColumnWidth(1, 500);
|
|
|
|
if (item)
|
|
{
|
|
mFileList->SetItemState(0,
|
|
wxLIST_STATE_FOCUSED | wxLIST_STATE_SELECTED,
|
|
wxLIST_STATE_FOCUSED | wxLIST_STATE_SELECTED);
|
|
mFileList->SetFocus();
|
|
}
|
|
}
|
|
|
|
bool AutoRecoveryDialog::HaveChecked()
|
|
{
|
|
long item = -1;
|
|
while (true)
|
|
{
|
|
item = mFileList->GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_DONTCARE);
|
|
if (item == wxNOT_FOUND)
|
|
{
|
|
break;
|
|
}
|
|
if (mFileList->IsItemChecked(item))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
AudacityMessageBox(XO("No projects selected"), XO("Automatic Crash Recovery"));
|
|
|
|
return false;
|
|
}
|
|
|
|
void AutoRecoveryDialog::OnQuitAudacity(wxCommandEvent &WXUNUSED(evt))
|
|
{
|
|
EndModal(ID_QUIT_AUDACITY);
|
|
}
|
|
|
|
void AutoRecoveryDialog::OnDiscardSelected(wxCommandEvent &WXUNUSED(evt))
|
|
{
|
|
if (!HaveChecked())
|
|
{
|
|
return;
|
|
}
|
|
|
|
long item = -1;
|
|
bool selectedTemporary = false;
|
|
while (!selectedTemporary)
|
|
{
|
|
item = mFileList->GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_DONTCARE);
|
|
if (item == wxNOT_FOUND)
|
|
break;
|
|
if (!mFileList->IsItemChecked(item))
|
|
continue;
|
|
FilePath fileName = mFiles[item];
|
|
wxFileName file(fileName);
|
|
if (file.GetExt().IsSameAs(FileNames::UnsavedProjectExtension()))
|
|
selectedTemporary = true;
|
|
}
|
|
|
|
// Don't give this warning message if all of the checked items are
|
|
// previously saved, non-temporary projects.
|
|
if (selectedTemporary) {
|
|
int ret = AudacityMessageBox(
|
|
XO("Are you sure you want to discard the selected projects?\n\n"
|
|
"Choosing \"Yes\" permanently deletes the selected projects immediately."),
|
|
XO("Automatic Crash Recovery"),
|
|
wxICON_QUESTION | wxYES_NO | wxNO_DEFAULT, this);
|
|
|
|
if (ret == wxNO)
|
|
return;
|
|
}
|
|
|
|
item = -1;
|
|
FilePaths files;
|
|
while (true)
|
|
{
|
|
item = mFileList->GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_DONTCARE);
|
|
if (item == wxNOT_FOUND)
|
|
{
|
|
break;
|
|
}
|
|
if (!mFileList->IsItemChecked(item))
|
|
{
|
|
// Keep in list
|
|
files.push_back(mFiles[item]);
|
|
continue;
|
|
}
|
|
FilePath fileName = mFiles[item];
|
|
|
|
// Only remove it from disk if it appears to be a temporary file.
|
|
wxFileName file(fileName);
|
|
if (file.GetExt().IsSameAs(FileNames::UnsavedProjectExtension()))
|
|
{
|
|
file.SetFullName(wxT(""));
|
|
|
|
wxFileName temp(TempDirectory::TempDir(), wxT(""));
|
|
if (file == temp)
|
|
ProjectFileIO::RemoveProject(fileName);
|
|
}
|
|
else
|
|
// Don't remove from disk, but do (later) open the database
|
|
// of this saved file, and discard edits
|
|
files.push_back(fileName);
|
|
|
|
// Forget all about it
|
|
ActiveProjects::Remove(fileName);
|
|
}
|
|
|
|
PopulateList();
|
|
|
|
mFiles = files;
|
|
|
|
if (mFileList->GetItemCount() == 0)
|
|
{
|
|
EndModal(ID_DISCARD_SELECTED);
|
|
}
|
|
}
|
|
|
|
void AutoRecoveryDialog::OnRecoverSelected(wxCommandEvent &WXUNUSED(evt))
|
|
{
|
|
if (!HaveChecked())
|
|
{
|
|
return;
|
|
}
|
|
|
|
FilePaths files;
|
|
|
|
bool selected = false;
|
|
long item = -1;
|
|
while (true)
|
|
{
|
|
item = mFileList->GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_DONTCARE);
|
|
if (item == wxNOT_FOUND)
|
|
{
|
|
if (!selected)
|
|
{
|
|
AudacityMessageBox(XO("No projects selected"), XO("Automatic Crash Recovery"));
|
|
}
|
|
break;
|
|
}
|
|
selected = true;
|
|
|
|
if (!mFileList->IsItemChecked(item))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
files.push_back(mFiles[item]);
|
|
}
|
|
|
|
mFiles = files;
|
|
|
|
EndModal(ID_RECOVER_SELECTED);
|
|
}
|
|
|
|
void AutoRecoveryDialog::OnSkip(wxCommandEvent &WXUNUSED(evt))
|
|
{
|
|
EndModal(ID_SKIP);
|
|
}
|
|
|
|
void AutoRecoveryDialog::OnColumnClicked(wxListEvent &evt)
|
|
{
|
|
if (evt.GetColumn() != 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
long item = -1;
|
|
while (true)
|
|
{
|
|
item = mFileList->GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_DONTCARE);
|
|
if (item == wxNOT_FOUND)
|
|
{
|
|
break;
|
|
}
|
|
mFileList->CheckItem(item, !mFileList->IsItemChecked(item));
|
|
}
|
|
}
|
|
|
|
void AutoRecoveryDialog::OnItemActivated(wxListEvent &evt)
|
|
{
|
|
long item = evt.GetIndex();
|
|
mFileList->CheckItem(item, !mFileList->IsItemChecked(item));
|
|
}
|
|
|
|
void AutoRecoveryDialog::OnListKeyDown(wxKeyEvent &evt)
|
|
{
|
|
switch (evt.GetKeyCode())
|
|
{
|
|
case WXK_SPACE:
|
|
{
|
|
bool selected = false;
|
|
long item = -1;
|
|
while (true)
|
|
{
|
|
item = mFileList->GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
|
|
if (item == wxNOT_FOUND)
|
|
{
|
|
break;
|
|
}
|
|
|
|
mFileList->CheckItem(item, !mFileList->IsItemChecked(item));
|
|
}
|
|
}
|
|
break;
|
|
|
|
case WXK_RETURN:
|
|
// Don't know why wxListCtrls prevent default dialog action,
|
|
// but they do, so handle it.
|
|
EmulateButtonClickIfPresent(GetAffirmativeId());
|
|
break;
|
|
|
|
default:
|
|
evt.Skip();
|
|
break;
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
|
|
static bool RecoverAllProjects(const FilePaths &files,
|
|
AudacityProject *&pproj)
|
|
{
|
|
// Open a project window for each auto save file
|
|
wxString filename;
|
|
bool result = true;
|
|
|
|
for (auto &file: files)
|
|
{
|
|
AudacityProject *proj = nullptr;
|
|
// Reuse any existing project window, which will be the empty project
|
|
// created at application startup
|
|
std::swap(proj, pproj);
|
|
|
|
// Open project.
|
|
if (ProjectManager::OpenProject(proj, file, false, true) == nullptr)
|
|
{
|
|
result = false;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static void DiscardAllProjects(const FilePaths &files)
|
|
{
|
|
// Open and close each file, invisibly, removing its Autosave blob
|
|
for (auto &file: files)
|
|
ProjectFileManager::DiscardAutosave(file);
|
|
}
|
|
|
|
bool ShowAutoRecoveryDialogIfNeeded(AudacityProject *&pproj, bool *didRecoverAnything)
|
|
{
|
|
if (didRecoverAnything)
|
|
{
|
|
*didRecoverAnything = false;
|
|
}
|
|
|
|
bool success = true;
|
|
|
|
// Under wxGTK3, the auto recovery dialog will not get
|
|
// the focus since the project window hasn't been allowed
|
|
// to completely initialize.
|
|
//
|
|
// Yielding seems to allow the initialization to complete.
|
|
//
|
|
// Additionally, it also corrects a sizing issue in the dialog
|
|
// related to wxWidgets bug:
|
|
//
|
|
// http://trac.wxwidgets.org/ticket/16440
|
|
//
|
|
// This must be done before "dlg" is declared.
|
|
wxEventLoopBase::GetActive()->YieldFor(wxEVT_CATEGORY_UI);
|
|
|
|
AutoRecoveryDialog dialog(pproj);
|
|
|
|
if (dialog.HasRecoverables())
|
|
{
|
|
int ret = dialog.ShowModal();
|
|
|
|
switch (ret)
|
|
{
|
|
case ID_SKIP:
|
|
success = true;
|
|
break;
|
|
|
|
case ID_DISCARD_SELECTED:
|
|
DiscardAllProjects(dialog.GetRecoverables());
|
|
success = true;
|
|
break;
|
|
|
|
case ID_RECOVER_SELECTED:
|
|
success = RecoverAllProjects(dialog.GetRecoverables(), pproj);
|
|
if (success)
|
|
{
|
|
if (didRecoverAnything)
|
|
{
|
|
*didRecoverAnything = true;
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
// This includes ID_QUIT_AUDACITY
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return success;
|
|
}
|