1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-11-06 17:13:49 +01:00
Files
audacity/src/AutoRecoveryDialog.cpp
Mart Raudsepp 4baa9dea93 AutoRecoveryDialog: Port to wxDataViewListCtrl
wxDataViewCtrl and co are preferred over legacy wxListCtrl/wxTreeCtrl.
wxDataViewCtrl gives a native wxGTK and wxMac widget, unlike wxListCtrl.
It also supports a checkboxes column in wxWidgets-3.0, for which
wxListCtrl only added support in 3.1.

Signed-off-by: Mart Raudsepp <leio@gentoo.org>
2021-09-05 00:53:00 +03:00

433 lines
10 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/dataview.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 Populate(ShuttleGui &S);
void PopulateList();
bool HaveChecked();
void OnQuitAudacity(wxCommandEvent &evt);
void OnDiscardSelected(wxCommandEvent &evt);
void OnRecoverSelected(wxCommandEvent &evt);
void OnSkip(wxCommandEvent &evt);
FilePaths mFiles;
wxDataViewListCtrl *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)
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);
Populate(S);
}
bool AutoRecoveryDialog::HasRecoverables() const
{
return mFiles.size() > 0;
}
FilePaths AutoRecoveryDialog::GetRecoverables()
{
return mFiles;
}
void AutoRecoveryDialog::Populate(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 = safenew wxDataViewListCtrl(this, ID_FILE_LIST);
mFileList->SetMinSize(wxSize(-1, 120));
mFileList->AppendToggleColumn(
/*i18n-hint: (verb). It instruct the user to select items.*/
_("Select")
);
mFileList->AppendTextColumn(
/*i18n-hint: (noun). It's the name of the project to recover.*/
_("Name")
);
S.Id(ID_FILE_LIST)
.Prop(1)
.Position(wxEXPAND | wxALL)
.AddWindow(mFileList);
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();
wxVector<wxVariant> data;
for (auto file : files)
{
wxFileName fn = file;
if (fn != activeFile)
{
// TODO: Consider storing the file paths as row data instead via
// second param of AppendItem or SetItemData
mFiles.push_back(fn.GetFullPath());
data.clear();
data.push_back(true); // Toggle enabled
data.push_back(fn.GetName());
mFileList->AppendItem(data);
}
}
if (mFiles.GetCount() > 0)
{
mFileList->SelectRow(0);
mFileList->SetFocus();
}
}
bool AutoRecoveryDialog::HaveChecked()
{
unsigned int cnt = mFileList->GetItemCount();
for (unsigned int row = 0; row < cnt; ++row)
{
if (mFileList->GetToggleValue(row, 0))
{
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;
}
unsigned int cnt = mFileList->GetItemCount();
bool selectedTemporary = false;
for (unsigned int row = 0; row < cnt; ++row)
{
if (!mFileList->GetToggleValue(row, 0))
continue;
FilePath fileName = mFiles[row];
wxFileName file(fileName);
if (file.GetExt().IsSameAs(FileNames::UnsavedProjectExtension()))
{
selectedTemporary = true;
break;
}
}
// 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;
}
FilePaths files;
for (unsigned int row = 0; row < cnt; ++row)
{
if (!mFileList->GetToggleValue(row, 0))
{
// Keep in list
files.push_back(mFiles[row]);
continue;
}
FilePath fileName = mFiles[row];
// 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;
unsigned int cnt = mFileList->GetItemCount();
for (unsigned int row = 0; row < cnt; ++row)
{
if (mFileList->GetToggleValue(row, 0))
{
files.push_back(mFiles[row]);
}
}
if (files.IsEmpty())
{
AudacityMessageBox(XO("No projects selected"), XO("Automatic Crash Recovery"));
}
mFiles = files;
EndModal(ID_RECOVER_SELECTED);
}
void AutoRecoveryDialog::OnSkip(wxCommandEvent &WXUNUSED(evt))
{
EndModal(ID_SKIP);
}
////////////////////////////////////////////////////////////////////////////
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;
}