Audacity 3.2.0
AutoRecoveryDialog.cpp
Go to the documentation of this file.
1/**********************************************************************
2
3Audacity: A Digital Audio Editor
4
5AutoRecoveryDialog.cpp
6
7Paul 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"
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
28enum {
34};
35
37{
38public:
39 explicit AutoRecoveryDialog(AudacityProject *proj);
40
41 bool HasRecoverables() const;
43
44private:
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
61public:
62 DECLARE_EVENT_TABLE()
63};
64
65BEGIN_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();
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 {
110 .ConnectRoot(wxEVT_KEY_DOWN, &AutoRecoveryDialog::OnListKeyDown)
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
231void AutoRecoveryDialog::OnQuitAudacity(wxCommandEvent &WXUNUSED(evt))
232{
233 EndModal(ID_QUIT_AUDACITY);
234}
235
236void 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
317void 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
354void 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
419static bool RecoverAllProjects(const FilePaths &files,
420 AudacityProject *&pproj)
421{
422 // Open a project window for each auto save file
423 wxString filename;
424 bool result = true;
425
426 for (auto &file: files)
427 {
428 AudacityProject *proj = nullptr;
429 // Reuse any existing project window, which will be the empty project
430 // created at application startup
431 std::swap(proj, pproj);
432
433 // Open project.
434 if (ProjectManager::OpenProject(proj, file, false, true) == nullptr)
435 {
436 result = false;
437 }
438 }
439
440 return result;
441}
442
443static void DiscardAllProjects(const FilePaths &files)
444{
445 // Open and close each file, invisibly, removing its Autosave blob
446 for (auto &file: files)
448}
449
450bool ShowAutoRecoveryDialogIfNeeded(AudacityProject *&pproj, bool *didRecoverAnything)
451{
452 if (didRecoverAnything)
453 {
454 *didRecoverAnything = false;
455 }
456
457 bool success = true;
458
459 // Under wxGTK3, the auto recovery dialog will not get
460 // the focus since the project window hasn't been allowed
461 // to completely initialize.
462 //
463 // Yielding seems to allow the initialization to complete.
464 //
465 // Additionally, it also corrects a sizing issue in the dialog
466 // related to wxWidgets bug:
467 //
468 // http://trac.wxwidgets.org/ticket/16440
469 //
470 // This must be done before "dlg" is declared.
471 wxEventLoopBase::GetActive()->YieldFor(wxEVT_CATEGORY_UI);
472
473 AutoRecoveryDialog dialog(pproj);
474
475 if (dialog.HasRecoverables())
476 {
477 int ret = dialog.ShowModal();
478
479 switch (ret)
480 {
481 case ID_SKIP:
482 success = true;
483 break;
484
487 success = true;
488 break;
489
491 success = RecoverAllProjects(dialog.GetRecoverables(), pproj);
492 if (success)
493 {
494 if (didRecoverAnything)
495 {
496 *didRecoverAnything = true;
497 }
498 }
499 break;
500
501 default:
502 // This includes ID_QUIT_AUDACITY
503 return false;
504 }
505 }
506
507 return success;
508}
int AudacityMessageBox(const TranslatableString &message, const TranslatableString &caption, long style, wxWindow *parent, int x, int y)
bool ShowAutoRecoveryDialogIfNeeded(AudacityProject *&pproj, bool *didRecoverAnything)
static void DiscardAllProjects(const FilePaths &files)
static bool RecoverAllProjects(const FilePaths &files, AudacityProject *&pproj)
@ ID_FILE_LIST
@ ID_RECOVER_SELECTED
@ ID_DISCARD_SELECTED
@ ID_QUIT_AUDACITY
END_EVENT_TABLE()
EVT_BUTTON(wxID_NO, DependencyDialog::OnNo) EVT_BUTTON(wxID_YES
EVT_LIST_ITEM_ACTIVATED(wxID_ANY, SuccessDialog::OnItemActivated) ExportMultipleDialog
#define XXO(s)
Definition: Internat.h:44
#define XO(s)
Definition: Internat.h:31
wxString FilePath
Definition: Project.h:20
@ eIsCreating
Definition: ShuttleGui.h:39
#define S(N)
Definition: ToChars.cpp:64
static const auto fn
The top-level handle to an Audacity project. It serves as a source of events that other objects can b...
Definition: Project.h:89
AutoRecoveryDialog(AudacityProject *proj)
void OnQuitAudacity(wxCommandEvent &evt)
void OnItemActivated(wxListEvent &evt)
void OnListKeyDown(wxKeyEvent &evt)
bool HasRecoverables() const
void PopulateOrExchange(ShuttleGui &S)
void OnDiscardSelected(wxCommandEvent &evt)
void OnRecoverSelected(wxCommandEvent &evt)
AudacityProject * mProject
void OnColumnClicked(wxListEvent &evt)
void OnSkip(wxCommandEvent &evt)
static bool RemoveProject(const FilePath &filename)
Remove any files associated with a project at given path; return true if successful.
static ProjectFileIO & Get(AudacityProject &project)
static void DiscardAutosave(const FilePath &filename)
static AudacityProject * OpenProject(AudacityProject *pGivenProject, const FilePath &fileNameArg, bool addtohistory, bool reuseNonemptyProject)
Open a file into an AudacityProject, returning the project, or nullptr for failure.
Derived from ShuttleGuiBase, an Audacity specific class for shuttling data to and from GUI.
Definition: ShuttleGui.h:631
Extend wxArrayString with move operations and construction and insertion fromstd::initializer_list.
FilePaths GetAll()
void Remove(const FilePath &path)
FILES_API wxString UnsavedProjectExtension()
FILES_API wxString TempDir()
void swap(std::unique_ptr< Alg_seq > &a, std::unique_ptr< Alg_seq > &b)
Definition: NoteTrack.cpp:753