Audacity  3.0.3
AutoRecoveryDialog.cpp
Go to the documentation of this file.
1 /**********************************************************************
2 
3 Audacity: A Digital Audio Editor
4 
5 AutoRecoveryDialog.cpp
6 
7 Paul Licameli split from AutoRecovery.cpp
8 
9 **********************************************************************/
10 
11 #include "AutoRecoveryDialog.h"
12 
13 #include "ActiveProjects.h"
14 #include "ProjectManager.h"
15 #include "ProjectFileIO.h"
16 #include "ProjectFileManager.h"
17 #include "ShuttleGui.h"
18 #include "TempDirectory.h"
20 #include "widgets/wxPanelWrapper.h"
21 
22 #include <wx/dir.h>
23 #include <wx/evtloop.h>
24 #include <wx/filefn.h>
25 #include <wx/filename.h>
26 #include <wx/listctrl.h>
27 
28 enum {
34 };
35 
36 class AutoRecoveryDialog final : public wxDialogWrapper
37 {
38 public:
40 
41  bool HasRecoverables() const;
43 
44 private:
46  void PopulateList();
47  bool HaveChecked();
48 
49  void OnQuitAudacity(wxCommandEvent &evt);
50  void OnDiscardSelected(wxCommandEvent &evt);
51  void OnRecoverSelected(wxCommandEvent &evt);
52  void OnSkip(wxCommandEvent &evt);
53  void OnColumnClicked(wxListEvent &evt);
54  void OnItemActivated(wxListEvent &evt);
55  void OnListKeyDown(wxKeyEvent &evt);
56 
58  wxListCtrl *mFileList;
60 
61 public:
62  DECLARE_EVENT_TABLE()
63 };
64 
65 BEGIN_EVENT_TABLE(AutoRecoveryDialog, wxDialogWrapper)
73 
75 : wxDialogWrapper(nullptr, wxID_ANY, XO("Automatic Crash Recovery"),
76  wxDefaultPosition, wxDefaultSize,
77  (wxDEFAULT_DIALOG_STYLE & (~wxCLOSE_BOX)) | wxRESIZE_BORDER), // no close box
78  mProject(project)
79 {
80  SetName();
81  ShuttleGui S(this, eIsCreating);
82  PopulateOrExchange(S);
83 }
84 
86 {
87  return mFiles.size() > 0;
88 }
89 
91 {
92  return mFiles;
93 }
94 
96 {
97  S.SetBorder(5);
98  S.StartVerticalLay(wxEXPAND, 1);
99  {
100  S.AddFixedText(
101  XO("The following projects were not saved properly the last time Audacity was run and "
102  "can be automatically recovered.\n\n"
103  "After recovery, save the projects to ensure changes are written to disk."),
104  false,
105  500);
106 
107  S.StartStatic(XO("Recoverable &projects"), 1);
108  {
111  .AddListControlReportMode(
112  {
113  /*i18n-hint: (verb). It instruct the user to select items.*/
114  XO("Select"),
115  /*i18n-hint: (noun). It's the name of the project to recover.*/
116  XO("Name")
117  });
118  mFileList->EnableCheckBoxes();
119  PopulateList();
120  }
121  S.EndStatic();
122 
123  S.StartHorizontalLay(wxALIGN_CENTRE, 0);
124  {
125  S.Id(ID_QUIT_AUDACITY).AddButton(XXO("&Quit Audacity"));
126  S.Id(ID_DISCARD_SELECTED).AddButton(XXO("&Discard Selected"));
127  S.Id(ID_RECOVER_SELECTED).AddButton(XXO("&Recover Selected"), wxALIGN_CENTRE, true);
128  S.Id(ID_SKIP).AddButton(XXO("&Skip"));
129 
130  SetAffirmativeId(ID_RECOVER_SELECTED);
131  SetEscapeId(ID_SKIP);
132  }
133  S.EndHorizontalLay();
134  }
135  S.EndVerticalLay();
136 
137  Layout();
138  Fit();
139  SetMinSize(GetSize());
140 
141  // Sometimes it centers on wxGTK and sometimes it doesn't.
142  // Yielding before centering seems to be a good workaround,
143  // but will leave to implement on a rainy day.
144  Center();
145 }
146 
148 {
149  wxString tempdir = TempDirectory::TempDir();
150  wxString pattern = wxT("*.") + FileNames::UnsavedProjectExtension();
151  FilePaths files;
152 
153  wxDir::GetAllFiles(tempdir, &files, pattern, wxDIR_FILES);
154 
156 
157  for (auto file : active)
158  {
159  wxFileName fn = file;
160  if (fn.FileExists())
161  {
162  FilePath fullPath = fn.GetFullPath();
163  if (files.Index(fullPath) == wxNOT_FOUND)
164  {
165  files.push_back(fullPath);
166  }
167  }
168  else
169  {
171  }
172  }
173 
174  FilePath activeFile;
175  if (mProject)
176  {
177  auto &projectFileIO = ProjectFileIO::Get(*mProject);
178  activeFile = projectFileIO.GetFileName();
179  }
180 
181  mFiles.clear();
182  mFileList->DeleteAllItems();
183  long item = 0;
184 
185  for (auto file : files)
186  {
187  wxFileName fn = file;
188  if (fn != activeFile)
189  {
190  mFiles.push_back(fn.GetFullPath());
191  mFileList->InsertItem(item, wxT(""));
192  mFileList->SetItem(item, 1, fn.GetName());
193  mFileList->CheckItem(item, true);
194  item++;
195  }
196  }
197  mFileList->SetMinSize(mFileList->GetBestSize());
198  mFileList->SetColumnWidth(0, wxLIST_AUTOSIZE_USEHEADER);
199  mFileList->SetColumnWidth(1, 500);
200 
201  if (item)
202  {
203  mFileList->SetItemState(0,
204  wxLIST_STATE_FOCUSED | wxLIST_STATE_SELECTED,
205  wxLIST_STATE_FOCUSED | wxLIST_STATE_SELECTED);
206  mFileList->SetFocus();
207  }
208 }
209 
211 {
212  long item = -1;
213  while (true)
214  {
215  item = mFileList->GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_DONTCARE);
216  if (item == wxNOT_FOUND)
217  {
218  break;
219  }
220  if (mFileList->IsItemChecked(item))
221  {
222  return true;
223  }
224  }
225 
226  AudacityMessageBox(XO("No projects selected"), XO("Automatic Crash Recovery"));
227 
228  return false;
229 }
230 
231 void AutoRecoveryDialog::OnQuitAudacity(wxCommandEvent &WXUNUSED(evt))
232 {
233  EndModal(ID_QUIT_AUDACITY);
234 }
235 
236 void AutoRecoveryDialog::OnDiscardSelected(wxCommandEvent &WXUNUSED(evt))
237 {
238  if (!HaveChecked())
239  {
240  return;
241  }
242 
243  long item = -1;
244  bool selectedTemporary = false;
245  while (!selectedTemporary)
246  {
247  item = mFileList->GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_DONTCARE);
248  if (item == wxNOT_FOUND)
249  break;
250  if (!mFileList->IsItemChecked(item))
251  continue;
252  FilePath fileName = mFiles[item];
253  wxFileName file(fileName);
254  if (file.GetExt().IsSameAs(FileNames::UnsavedProjectExtension()))
255  selectedTemporary = true;
256  }
257 
258  // Don't give this warning message if all of the checked items are
259  // previously saved, non-temporary projects.
260  if (selectedTemporary) {
261  int ret = AudacityMessageBox(
262  XO("Are you sure you want to discard the selected projects?\n\n"
263  "Choosing \"Yes\" permanently deletes the selected projects immediately."),
264  XO("Automatic Crash Recovery"),
265  wxICON_QUESTION | wxYES_NO | wxNO_DEFAULT, this);
266 
267  if (ret == wxNO)
268  return;
269  }
270 
271  item = -1;
272  FilePaths files;
273  while (true)
274  {
275  item = mFileList->GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_DONTCARE);
276  if (item == wxNOT_FOUND)
277  {
278  break;
279  }
280  if (!mFileList->IsItemChecked(item))
281  {
282  // Keep in list
283  files.push_back(mFiles[item]);
284  continue;
285  }
286  FilePath fileName = mFiles[item];
287 
288  // Only remove it from disk if it appears to be a temporary file.
289  wxFileName file(fileName);
290  if (file.GetExt().IsSameAs(FileNames::UnsavedProjectExtension()))
291  {
292  file.SetFullName(wxT(""));
293 
294  wxFileName temp(TempDirectory::TempDir(), wxT(""));
295  if (file == temp)
297  }
298  else
299  // Don't remove from disk, but do (later) open the database
300  // of this saved file, and discard edits
301  files.push_back(fileName);
302 
303  // Forget all about it
304  ActiveProjects::Remove(fileName);
305  }
306 
307  PopulateList();
308 
309  mFiles = files;
310 
311  if (mFileList->GetItemCount() == 0)
312  {
313  EndModal(ID_DISCARD_SELECTED);
314  }
315 }
316 
317 void AutoRecoveryDialog::OnRecoverSelected(wxCommandEvent &WXUNUSED(evt))
318 {
319  if (!HaveChecked())
320  {
321  return;
322  }
323 
324  FilePaths files;
325 
326  bool selected = false;
327  long item = -1;
328  while (true)
329  {
330  item = mFileList->GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_DONTCARE);
331  if (item == wxNOT_FOUND)
332  {
333  if (!selected)
334  {
335  AudacityMessageBox(XO("No projects selected"), XO("Automatic Crash Recovery"));
336  }
337  break;
338  }
339  selected = true;
340 
341  if (!mFileList->IsItemChecked(item))
342  {
343  continue;
344  }
345 
346  files.push_back(mFiles[item]);
347  }
348 
349  mFiles = files;
350 
351  EndModal(ID_RECOVER_SELECTED);
352 }
353 
354 void AutoRecoveryDialog::OnSkip(wxCommandEvent &WXUNUSED(evt))
355 {
356  EndModal(ID_SKIP);
357 }
358 
360 {
361  if (evt.GetColumn() != 0)
362  {
363  return;
364  }
365 
366  long item = -1;
367  while (true)
368  {
369  item = mFileList->GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_DONTCARE);
370  if (item == wxNOT_FOUND)
371  {
372  break;
373  }
374  mFileList->CheckItem(item, !mFileList->IsItemChecked(item));
375  }
376 }
377 
379 {
380  long item = evt.GetIndex();
381  mFileList->CheckItem(item, !mFileList->IsItemChecked(item));
382 }
383 
385 {
386  switch (evt.GetKeyCode())
387  {
388  case WXK_SPACE:
389  {
390  bool selected = false;
391  long item = -1;
392  while (true)
393  {
394  item = mFileList->GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
395  if (item == wxNOT_FOUND)
396  {
397  break;
398  }
399 
400  mFileList->CheckItem(item, !mFileList->IsItemChecked(item));
401  }
402  }
403  break;
404 
405  case WXK_RETURN:
406  // Don't know why wxListCtrls prevent default dialog action,
407  // but they do, so handle it.
408  EmulateButtonClickIfPresent(GetAffirmativeId());
409  break;
410 
411  default:
412  evt.Skip();
413  break;
414  }
415 }
416 
418 
419 static bool RecoverAllProjects(const FilePaths &files,
420  AudacityProject **pproj)
421 {
422  // Open a project window for each auto save file
423  wxString filename;
424 
425  for (auto &file: files)
426  {
427  AudacityProject *proj = nullptr;
428  if (*pproj)
429  {
430  // Reuse existing project window
431  proj = *pproj;
432  *pproj = NULL;
433  }
434 
435  // Open project.
436  if (ProjectManager::OpenProject(proj, file, false) == nullptr)
437  {
438  return false;
439  }
440  }
441 
442  return true;
443 }
444 
445 static void DiscardAllProjects(const FilePaths &files)
446 {
447  // Open and close each file, invisibly, removing its Autosave blob
448  for (auto &file: files)
450 }
451 
452 bool ShowAutoRecoveryDialogIfNeeded(AudacityProject **pproj, bool *didRecoverAnything)
453 {
454  if (didRecoverAnything)
455  {
456  *didRecoverAnything = false;
457  }
458 
459  bool success = true;
460 
461  // Under wxGTK3, the auto recovery dialog will not get
462  // the focus since the project window hasn't been allowed
463  // to completely initialize.
464  //
465  // Yielding seems to allow the initialization to complete.
466  //
467  // Additionally, it also corrects a sizing issue in the dialog
468  // related to wxWidgets bug:
469  //
470  // http://trac.wxwidgets.org/ticket/16440
471  //
472  // This must be done before "dlg" is declared.
473  wxEventLoopBase::GetActive()->YieldFor(wxEVT_CATEGORY_UI);
474 
475  AutoRecoveryDialog dialog(*pproj);
476 
477  if (dialog.HasRecoverables())
478  {
479  int ret = dialog.ShowModal();
480 
481  switch (ret)
482  {
483  case ID_SKIP:
484  success = true;
485  break;
486 
487  case ID_DISCARD_SELECTED:
489  success = true;
490  break;
491 
492  case ID_RECOVER_SELECTED:
493  success = RecoverAllProjects(dialog.GetRecoverables(), pproj);
494  if (success)
495  {
496  if (didRecoverAnything)
497  {
498  *didRecoverAnything = true;
499  }
500  }
501  break;
502 
503  default:
504  // This includes ID_QUIT_AUDACITY
505  return false;
506  }
507  }
508 
509  return success;
510 }
EVT_BUTTON
EVT_BUTTON(wxID_NO, DependencyDialog::OnNo) EVT_BUTTON(wxID_YES
AutoRecoveryDialog::GetRecoverables
FilePaths GetRecoverables()
Definition: AutoRecoveryDialog.cpp:90
eIsCreating
@ eIsCreating
Definition: ShuttleGui.h:36
AutoRecoveryDialog::mFileList
wxListCtrl * mFileList
Definition: AutoRecoveryDialog.cpp:58
ShuttleGuiBase::StartVerticalLay
void StartVerticalLay(int iProp=1)
Definition: ShuttleGui.cpp:1177
ProjectFileIO.h
ID_DISCARD_SELECTED
@ ID_DISCARD_SELECTED
Definition: AutoRecoveryDialog.cpp:30
fn
static const auto fn
Definition: WaveformView.cpp:1102
AudacityMessageBox
int AudacityMessageBox(const TranslatableString &message, const TranslatableString &caption=AudacityMessageBoxCaptionStr(), long style=wxOK|wxCENTRE, wxWindow *parent=NULL, int x=wxDefaultCoord, int y=wxDefaultCoord)
Definition: AudacityMessageBox.h:20
AutoRecoveryDialog::OnRecoverSelected
void OnRecoverSelected(wxCommandEvent &evt)
Definition: AutoRecoveryDialog.cpp:317
ActiveProjects.h
ProjectFileIO::RemoveProject
static bool RemoveProject(const FilePath &filename)
Remove any files associated with a project at given path; return true if successful.
Definition: ProjectFileIO.cpp:1198
FileNames::UnsavedProjectExtension
wxString UnsavedProjectExtension()
ID_RECOVER_SELECTED
@ ID_RECOVER_SELECTED
Definition: AutoRecoveryDialog.cpp:31
XO
#define XO(s)
Definition: Internat.h:32
wxPanelWrapper.h
ProjectFileIO::Get
static ProjectFileIO & Get(AudacityProject &project)
Definition: ProjectFileIO.cpp:267
wxArrayStringEx
Definition: MemoryX.h:663
AutoRecoveryDialog::OnDiscardSelected
void OnDiscardSelected(wxCommandEvent &evt)
Definition: AutoRecoveryDialog.cpp:236
ShuttleGui::Id
ShuttleGui & Id(int id)
Definition: ShuttleGui.cpp:2248
XXO
#define XXO(s)
Definition: Internat.h:45
ShuttleGuiBase::EndHorizontalLay
void EndHorizontalLay()
Definition: ShuttleGui.cpp:1170
ShuttleGuiBase::StartHorizontalLay
void StartHorizontalLay(int PositionFlags=wxALIGN_CENTRE, int iProp=1)
Definition: ShuttleGui.cpp:1160
ShuttleGuiBase::EndVerticalLay
void EndVerticalLay()
Definition: ShuttleGui.cpp:1196
AutoRecoveryDialog::OnColumnClicked
void OnColumnClicked(wxListEvent &evt)
Definition: AutoRecoveryDialog.cpp:359
ShuttleGuiBase::AddFixedText
void AddFixedText(const TranslatableString &Str, bool bCenter=false, int wrapWidth=0)
Definition: ShuttleGui.cpp:433
AutoRecoveryDialog::AutoRecoveryDialog
AutoRecoveryDialog(AudacityProject *proj)
Definition: AutoRecoveryDialog.cpp:74
ProjectFileManager.h
ProjectManager::OpenProject
static AudacityProject * OpenProject(AudacityProject *pProject, const FilePath &fileNameArg, bool addtohistory=true)
Definition: ProjectManager.cpp:901
ProjectFileManager::DiscardAutosave
static void DiscardAutosave(const FilePath &filename)
Definition: ProjectFileManager.cpp:66
AutoRecoveryDialog::OnListKeyDown
void OnListKeyDown(wxKeyEvent &evt)
Definition: AutoRecoveryDialog.cpp:384
TempDirectory.h
RecoverAllProjects
static bool RecoverAllProjects(const FilePaths &files, AudacityProject **pproj)
Definition: AutoRecoveryDialog.cpp:419
ShuttleGui.h
ProjectManager.h
AutoRecoveryDialog::OnSkip
void OnSkip(wxCommandEvent &evt)
Definition: AutoRecoveryDialog.cpp:354
ActiveProjects::GetAll
FilePaths GetAll()
DiscardAllProjects
static void DiscardAllProjects(const FilePaths &files)
Definition: AutoRecoveryDialog.cpp:445
AutoRecoveryDialog::mFiles
FilePaths mFiles
Definition: AutoRecoveryDialog.cpp:57
ShuttleGuiBase::AddButton
wxButton * AddButton(const TranslatableString &Text, int PositionFlags=wxALIGN_CENTRE, bool setDefault=false)
Definition: ShuttleGui.cpp:353
ShuttleGuiBase::StartStatic
wxStaticBox * StartStatic(const TranslatableString &Str, int iProp=0)
Definition: ShuttleGui.cpp:886
AutoRecoveryDialog
Definition: AutoRecoveryDialog.cpp:37
FilePath
wxString FilePath
Definition: Types.h:270
wxDialogWrapper
Definition: wxPanelWrapper.h:81
ShuttleGui::ConnectRoot
auto ConnectRoot(wxEventTypeTag< Tag > eventType, void(Handler::*func)(Argument &)) -> typename std::enable_if< std::is_base_of< Argument, Tag >::value, ShuttleGui & >::type
Definition: ShuttleGui.h:706
AutoRecoveryDialog::PopulateOrExchange
void PopulateOrExchange(ShuttleGui &S)
Definition: AutoRecoveryDialog.cpp:95
AudacityProject
The top-level handle to an Audacity project. It serves as a source of events that other objects can b...
Definition: Project.h:112
AutoRecoveryDialog::HaveChecked
bool HaveChecked()
Definition: AutoRecoveryDialog.cpp:210
AutoRecoveryDialog::PopulateList
void PopulateList()
Definition: AutoRecoveryDialog.cpp:147
AudacityMessageBox.h
ShowAutoRecoveryDialogIfNeeded
bool ShowAutoRecoveryDialogIfNeeded(AudacityProject **pproj, bool *didRecoverAnything)
Definition: AutoRecoveryDialog.cpp:452
AutoRecoveryDialog.h
AutoRecoveryDialog::OnItemActivated
void OnItemActivated(wxListEvent &evt)
Definition: AutoRecoveryDialog.cpp:378
EVT_LIST_ITEM_ACTIVATED
EVT_LIST_ITEM_ACTIVATED(wxID_ANY, SuccessDialog::OnItemActivated) ExportMultipleDialog
Definition: ExportMultiple.cpp:121
ShuttleGuiBase::SetBorder
void SetBorder(int Border)
Definition: ShuttleGui.h:497
ActiveProjects::Remove
void Remove(const FilePath &path)
ID_SKIP
@ ID_SKIP
Definition: AutoRecoveryDialog.cpp:32
AutoRecoveryDialog::OnQuitAudacity
void OnQuitAudacity(wxCommandEvent &evt)
Definition: AutoRecoveryDialog.cpp:231
ShuttleGuiBase::EndStatic
void EndStatic()
Definition: ShuttleGui.cpp:915
ID_FILE_LIST
@ ID_FILE_LIST
Definition: AutoRecoveryDialog.cpp:33
END_EVENT_TABLE
END_EVENT_TABLE()
ShuttleGui
Derived from ShuttleGuiBase, an Audacity specific class for shuttling data to and from GUI.
Definition: ShuttleGui.h:638
AutoRecoveryDialog::HasRecoverables
bool HasRecoverables() const
Definition: AutoRecoveryDialog.cpp:85
ID_QUIT_AUDACITY
@ ID_QUIT_AUDACITY
Definition: AutoRecoveryDialog.cpp:29
TempDirectory::TempDir
wxString TempDir()
Definition: TempDirectory.cpp:26
AutoRecoveryDialog::mProject
AudacityProject * mProject
Definition: AutoRecoveryDialog.cpp:59