Lower functions from ProjectManager to ProjectFileManager...
... not improving the dependency graph, but it's a more sensible division of duties, keeping related functions together.
This commit is contained in:
parent
27eeb1035c
commit
8b70203e45
|
@ -84,6 +84,7 @@ It handles initialization and termination by subclassing wxApp.
|
|||
#include "PluginManager.h"
|
||||
#include "Project.h"
|
||||
#include "ProjectAudioIO.h"
|
||||
#include "ProjectFileManager.h"
|
||||
#include "ProjectHistory.h"
|
||||
#include "ProjectManager.h"
|
||||
#include "ProjectSettings.h"
|
||||
|
@ -871,7 +872,7 @@ END_EVENT_TABLE()
|
|||
// - Inform the user if DefaultOpenPath not set.
|
||||
// - Switch focus to correct instance of project window, if already open.
|
||||
bool AudacityApp::MRUOpen(const FilePath &fullPathStr) {
|
||||
// Most of the checks below are copied from AudacityProject::OpenFiles.
|
||||
// Most of the checks below are copied from ProjectManager::OpenFiles.
|
||||
// - some rationalisation might be possible.
|
||||
|
||||
AudacityProject *proj = GetActiveProject();
|
||||
|
@ -887,7 +888,7 @@ bool AudacityApp::MRUOpen(const FilePath &fullPathStr) {
|
|||
// Test here even though AudacityProject::OpenFile() also now checks, because
|
||||
// that method does not return the bad result.
|
||||
// That itself may be a FIXME.
|
||||
if (ProjectManager::IsAlreadyOpen(fullPathStr))
|
||||
if (ProjectFileManager::IsAlreadyOpen(fullPathStr))
|
||||
return false;
|
||||
|
||||
// DMM: If the project is dirty, that means it's been touched at
|
||||
|
@ -946,7 +947,7 @@ void AudacityApp::OnMRUFile(wxCommandEvent& event) {
|
|||
// PRL: Don't call SafeMRUOpen
|
||||
// -- if open fails for some exceptional reason of resource exhaustion that
|
||||
// the user can correct, leave the file in history.
|
||||
if (!ProjectManager::IsAlreadyOpen(fullPathStr) && !MRUOpen(fullPathStr))
|
||||
if (!ProjectFileManager::IsAlreadyOpen(fullPathStr) && !MRUOpen(fullPathStr))
|
||||
history.RemoveFileFromHistory(n);
|
||||
}
|
||||
|
||||
|
|
|
@ -42,6 +42,7 @@
|
|||
#include "Menus.h"
|
||||
#include "Prefs.h"
|
||||
#include "Project.h"
|
||||
#include "ProjectFileManager.h"
|
||||
#include "ProjectHistory.h"
|
||||
#include "ProjectManager.h"
|
||||
#include "ProjectWindow.h"
|
||||
|
@ -464,7 +465,7 @@ void ApplyMacroDialog::OnApplyToFiles(wxCommandEvent & WXUNUSED(event))
|
|||
fileList->EnsureVisible(i);
|
||||
|
||||
auto success = GuardedCall< bool >( [&] {
|
||||
ProjectManager::Get( *project ).Import(files[i]);
|
||||
ProjectFileManager::Get( *project ).Import(files[i]);
|
||||
ProjectWindow::Get( *project ).ZoomAfterImport(nullptr);
|
||||
SelectActions::DoSelectAll(*project);
|
||||
if (!mMacroCommands.ApplyMacro(mCatalog))
|
||||
|
|
|
@ -13,22 +13,43 @@ Paul Licameli split from AudacityProject.cpp
|
|||
#include "Experimental.h"
|
||||
|
||||
#include <wx/crt.h> // for wxPrintf
|
||||
|
||||
#if defined(__WXGTK__)
|
||||
#include <wx/evtloop.h>
|
||||
#endif
|
||||
|
||||
#include <wx/frame.h>
|
||||
#include "AutoRecovery.h"
|
||||
#include "Dependencies.h"
|
||||
#include "DirManager.h"
|
||||
#include "FileNames.h"
|
||||
#include "Legacy.h"
|
||||
#include "Menus.h"
|
||||
#include "PlatformCompatibility.h"
|
||||
#include "Project.h"
|
||||
#include "ProjectFileIO.h"
|
||||
#include "ProjectFileIORegistry.h"
|
||||
#include "ProjectFSCK.h"
|
||||
#include "ProjectHistory.h"
|
||||
#include "ProjectSelectionManager.h"
|
||||
#include "ProjectSettings.h"
|
||||
#include "ProjectWindow.h"
|
||||
#include "SelectionState.h"
|
||||
#include "Sequence.h"
|
||||
#include "Tags.h"
|
||||
#include "TrackPanel.h"
|
||||
#include "UndoManager.h"
|
||||
#include "WaveClip.h"
|
||||
#include "WaveTrack.h"
|
||||
#include "wxFileNameWrapper.h"
|
||||
#include "effects/EffectManager.h"
|
||||
#include "export/Export.h"
|
||||
#include "import/Import.h"
|
||||
#include "commands/CommandContext.h"
|
||||
#include "ondemand/ODComputeSummaryTask.h"
|
||||
#include "ondemand/ODManager.h"
|
||||
#include "ondemand/ODTask.h"
|
||||
#include "toolbars/SelectionBar.h"
|
||||
#include "widgets/AudacityMessageBox.h"
|
||||
#include "widgets/ErrorDialog.h"
|
||||
#include "widgets/FileHistory.h"
|
||||
|
@ -914,3 +935,768 @@ void ProjectFileManager::CloseLock()
|
|||
mLastSavedTracks.reset();
|
||||
}
|
||||
}
|
||||
|
||||
// static method, can be called outside of a project
|
||||
wxArrayString ProjectFileManager::ShowOpenDialog(const wxString &extraformat, const wxString &extrafilter)
|
||||
{
|
||||
FormatList l;
|
||||
wxString filter; ///< List of file format names and extensions, separated
|
||||
/// by | characters between _formats_ and extensions for each _format_, i.e.
|
||||
/// format1name | *.ext | format2name | *.ex1;*.ex2
|
||||
wxString all; ///< One long list of all supported file extensions,
|
||||
/// semicolon separated
|
||||
|
||||
if (!extraformat.empty())
|
||||
{ // additional format specified
|
||||
all = extrafilter + wxT(';');
|
||||
// add it to the "all supported files" filter string
|
||||
}
|
||||
|
||||
// Construct the filter
|
||||
Importer::Get().GetSupportedImportFormats(&l);
|
||||
|
||||
for (const auto &format : l) {
|
||||
/* this loop runs once per supported _format_ */
|
||||
const Format *f = &format;
|
||||
|
||||
wxString newfilter = f->formatName + wxT("|");
|
||||
// bung format name into string plus | separator
|
||||
for (size_t i = 0; i < f->formatExtensions.size(); i++) {
|
||||
/* this loop runs once per valid _file extension_ for file containing
|
||||
* the current _format_ */
|
||||
if (!newfilter.Contains(wxT("*.") + f->formatExtensions[i] + wxT(";")))
|
||||
newfilter += wxT("*.") + f->formatExtensions[i] + wxT(";");
|
||||
if (!all.Contains(wxT("*.") + f->formatExtensions[i] + wxT(";")))
|
||||
all += wxT("*.") + f->formatExtensions[i] + wxT(";");
|
||||
}
|
||||
newfilter.RemoveLast(1);
|
||||
filter += newfilter;
|
||||
filter += wxT("|");
|
||||
}
|
||||
all.RemoveLast(1);
|
||||
filter.RemoveLast(1);
|
||||
|
||||
// For testing long filters
|
||||
#if 0
|
||||
wxString test = wxT("*.aaa;*.bbb;*.ccc;*.ddd;*.eee");
|
||||
all = test + wxT(';') + test + wxT(';') + test + wxT(';') +
|
||||
test + wxT(';') + test + wxT(';') + test + wxT(';') +
|
||||
test + wxT(';') + test + wxT(';') + test + wxT(';') +
|
||||
all;
|
||||
#endif
|
||||
|
||||
/* i18n-hint: The vertical bars and * are essential here.*/
|
||||
wxString mask = _("All files|*|All supported files|") +
|
||||
all + wxT("|"); // "all" and "all supported" entries
|
||||
if (!extraformat.empty())
|
||||
{ // append caller-defined format if supplied
|
||||
mask += extraformat + wxT("|") + extrafilter + wxT("|");
|
||||
}
|
||||
mask += filter; // put the names and extensions of all the importer formats
|
||||
// we built up earlier into the mask
|
||||
|
||||
// Retrieve saved path and type
|
||||
auto path = FileNames::FindDefaultPath(FileNames::Operation::Open);
|
||||
wxString type = gPrefs->Read(wxT("/DefaultOpenType"),mask.BeforeFirst(wxT('|')));
|
||||
|
||||
// Convert the type to the filter index
|
||||
int index = mask.First(type + wxT("|"));
|
||||
if (index == wxNOT_FOUND) {
|
||||
index = 0;
|
||||
}
|
||||
else {
|
||||
index = mask.Left(index).Freq(wxT('|')) / 2;
|
||||
if (index < 0) {
|
||||
index = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Construct and display the file dialog
|
||||
wxArrayString selected;
|
||||
|
||||
FileDialogWrapper dlog(NULL,
|
||||
_("Select one or more files"),
|
||||
path,
|
||||
wxT(""),
|
||||
mask,
|
||||
wxFD_OPEN | wxFD_MULTIPLE | wxRESIZE_BORDER);
|
||||
|
||||
dlog.SetFilterIndex(index);
|
||||
|
||||
int dialogResult = dlog.ShowModal();
|
||||
|
||||
// Convert the filter index to type and save
|
||||
index = dlog.GetFilterIndex();
|
||||
for (int i = 0; i < index; i++) {
|
||||
mask = mask.AfterFirst(wxT('|')).AfterFirst(wxT('|'));
|
||||
}
|
||||
gPrefs->Write(wxT("/DefaultOpenType"), mask.BeforeFirst(wxT('|')));
|
||||
gPrefs->Write(wxT("/LastOpenType"), mask.BeforeFirst(wxT('|')));
|
||||
gPrefs->Flush();
|
||||
|
||||
if (dialogResult == wxID_OK) {
|
||||
// Return the selected files
|
||||
dlog.GetPaths(selected);
|
||||
}
|
||||
return selected;
|
||||
}
|
||||
|
||||
// static method, can be called outside of a project
|
||||
bool ProjectFileManager::IsAlreadyOpen(const FilePath &projPathName)
|
||||
{
|
||||
const wxFileName newProjPathName(projPathName);
|
||||
auto start = AllProjects{}.begin(), finish = AllProjects{}.end(),
|
||||
iter = std::find_if( start, finish,
|
||||
[&]( const AllProjects::value_type &ptr ){
|
||||
return newProjPathName.SameAs(wxFileNameWrapper{ ptr->GetFileName() });
|
||||
} );
|
||||
if (iter != finish) {
|
||||
wxString errMsg =
|
||||
wxString::Format(_("%s is already open in another window."),
|
||||
newProjPathName.GetName());
|
||||
wxLogError(errMsg);
|
||||
AudacityMessageBox(errMsg, _("Error Opening Project"), wxOK | wxCENTRE);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
XMLTagHandler *
|
||||
ProjectFileManager::RecordingRecoveryFactory( AudacityProject &project ) {
|
||||
auto &ProjectFileManager = Get( project );
|
||||
auto &ptr = ProjectFileManager.mRecordingRecoveryHandler;
|
||||
if (!ptr)
|
||||
ptr =
|
||||
std::make_unique<RecordingRecoveryHandler>( &project );
|
||||
return ptr.get();
|
||||
}
|
||||
|
||||
ProjectFileIORegistry::Entry
|
||||
ProjectFileManager::sRecoveryFactory{
|
||||
wxT("recordingrecovery"), RecordingRecoveryFactory
|
||||
};
|
||||
|
||||
// XML handler for <import> tag
|
||||
class ImportXMLTagHandler final : public XMLTagHandler
|
||||
{
|
||||
public:
|
||||
ImportXMLTagHandler(AudacityProject* pProject) { mProject = pProject; }
|
||||
|
||||
bool HandleXMLTag(const wxChar *tag, const wxChar **attrs) override;
|
||||
XMLTagHandler *HandleXMLChild(const wxChar * WXUNUSED(tag)) override
|
||||
{ return NULL; }
|
||||
|
||||
// Don't want a WriteXML method because ImportXMLTagHandler is not a WaveTrack.
|
||||
// <import> tags are instead written by AudacityProject::WriteXML.
|
||||
// void WriteXML(XMLWriter &xmlFile) /* not override */ { wxASSERT(false); }
|
||||
|
||||
private:
|
||||
AudacityProject* mProject;
|
||||
};
|
||||
|
||||
bool ImportXMLTagHandler::HandleXMLTag(const wxChar *tag, const wxChar **attrs)
|
||||
{
|
||||
if (wxStrcmp(tag, wxT("import")) || attrs==NULL || (*attrs)==NULL || wxStrcmp(*attrs++, wxT("filename")))
|
||||
return false;
|
||||
wxString strAttr = *attrs;
|
||||
if (!XMLValueChecker::IsGoodPathName(strAttr))
|
||||
{
|
||||
// Maybe strAttr is just a fileName, not the full path. Try the project data directory.
|
||||
wxFileNameWrapper fileName{
|
||||
DirManager::Get( *mProject ).GetProjectDataDir(), strAttr };
|
||||
if (XMLValueChecker::IsGoodFileName(strAttr, fileName.GetPath(wxPATH_GET_VOLUME)))
|
||||
strAttr = fileName.GetFullPath();
|
||||
else
|
||||
{
|
||||
wxLogWarning(wxT("Could not import file: %s"), strAttr);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
WaveTrackArray trackArray;
|
||||
|
||||
// Guard this call so that C++ exceptions don't propagate through
|
||||
// the expat library
|
||||
GuardedCall(
|
||||
[&] {
|
||||
ProjectFileManager::Get( *mProject ).Import(strAttr, &trackArray); },
|
||||
[&] (AudacityException*) { trackArray.clear(); }
|
||||
);
|
||||
|
||||
if (trackArray.empty())
|
||||
return false;
|
||||
|
||||
// Handle other attributes, now that we have the tracks.
|
||||
attrs++;
|
||||
const wxChar** pAttr;
|
||||
bool bSuccess = true;
|
||||
|
||||
for (size_t i = 0; i < trackArray.size(); i++)
|
||||
{
|
||||
// Most of the "import" tag attributes are the same as for "wavetrack" tags,
|
||||
// so apply them via WaveTrack::HandleXMLTag().
|
||||
bSuccess = trackArray[i]->HandleXMLTag(wxT("wavetrack"), attrs);
|
||||
|
||||
// "offset" tag is ignored in WaveTrack::HandleXMLTag except for legacy projects,
|
||||
// so handle it here.
|
||||
double dblValue;
|
||||
pAttr = attrs;
|
||||
while (*pAttr)
|
||||
{
|
||||
const wxChar *attr = *pAttr++;
|
||||
const wxChar *value = *pAttr++;
|
||||
const wxString strValue = value;
|
||||
if (!wxStrcmp(attr, wxT("offset")) &&
|
||||
XMLValueChecker::IsGoodString(strValue) &&
|
||||
Internat::CompatibleToDouble(strValue, &dblValue))
|
||||
trackArray[i]->SetOffset(dblValue);
|
||||
}
|
||||
}
|
||||
return bSuccess;
|
||||
};
|
||||
|
||||
XMLTagHandler *
|
||||
ProjectFileManager::ImportHandlerFactory( AudacityProject &project ) {
|
||||
auto &ProjectFileManager = Get( project );
|
||||
auto &ptr = ProjectFileManager.mImportXMLTagHandler;
|
||||
if (!ptr)
|
||||
ptr =
|
||||
std::make_unique<ImportXMLTagHandler>( &project );
|
||||
return ptr.get();
|
||||
}
|
||||
|
||||
ProjectFileIORegistry::Entry
|
||||
ProjectFileManager::sImportHandlerFactory{
|
||||
wxT("import"), ImportHandlerFactory
|
||||
};
|
||||
|
||||
// FIXME:? TRAP_ERR This should return a result that is checked.
|
||||
// See comment in AudacityApp::MRUOpen().
|
||||
void ProjectFileManager::OpenFile(const FilePath &fileNameArg, bool addtohistory)
|
||||
{
|
||||
auto &project = mProject;
|
||||
auto &history = ProjectHistory::Get( project );
|
||||
auto &projectFileIO = ProjectFileIO::Get( project );
|
||||
auto &tracks = TrackList::Get( project );
|
||||
auto &trackPanel = TrackPanel::Get( project );
|
||||
auto &dirManager = DirManager::Get( project );
|
||||
auto &window = ProjectWindow::Get( project );
|
||||
|
||||
// On Win32, we may be given a short (DOS-compatible) file name on rare
|
||||
// occassions (e.g. stuff like "C:\PROGRA~1\AUDACI~1\PROJEC~1.AUP"). We
|
||||
// convert these to long file name first.
|
||||
auto fileName = PlatformCompatibility::ConvertSlashInFileName(
|
||||
PlatformCompatibility::GetLongFileName(fileNameArg));
|
||||
|
||||
// Make sure it isn't already open.
|
||||
// Vaughan, 2011-03-25: This was done previously in AudacityProject::OpenFiles()
|
||||
// and AudacityApp::MRUOpen(), but if you open an aup file by double-clicking it
|
||||
// from, e.g., Win Explorer, it would bypass those, get to here with no check,
|
||||
// then open a NEW project from the same data with no warning.
|
||||
// This was reported in http://bugzilla.audacityteam.org/show_bug.cgi?id=137#c17,
|
||||
// but is not really part of that bug. Anyway, prevent it!
|
||||
if (IsAlreadyOpen(fileName))
|
||||
return;
|
||||
|
||||
|
||||
// Data loss may occur if users mistakenly try to open ".aup.bak" files
|
||||
// left over from an unsuccessful save or by previous versions of Audacity.
|
||||
// So we always refuse to open such files.
|
||||
if (fileName.Lower().EndsWith(wxT(".aup.bak")))
|
||||
{
|
||||
AudacityMessageBox(
|
||||
_("You are trying to open an automatically created backup file.\nDoing this may result in severe data loss.\n\nPlease open the actual Audacity project file instead."),
|
||||
_("Warning - Backup File Detected"),
|
||||
wxOK | wxCENTRE, &window);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!::wxFileExists(fileName)) {
|
||||
AudacityMessageBox(
|
||||
wxString::Format( _("Could not open file: %s"), fileName ),
|
||||
("Error Opening File"),
|
||||
wxOK | wxCENTRE, &window);
|
||||
return;
|
||||
}
|
||||
|
||||
// We want to open projects using wxTextFile, but if it's NOT a project
|
||||
// file (but actually a WAV file, for example), then wxTextFile will spin
|
||||
// for a long time searching for line breaks. So, we look for our
|
||||
// signature at the beginning of the file first:
|
||||
|
||||
char buf[16];
|
||||
{
|
||||
wxFFile ff(fileName, wxT("rb"));
|
||||
if (!ff.IsOpened()) {
|
||||
AudacityMessageBox(
|
||||
wxString::Format( _("Could not open file: %s"), fileName ),
|
||||
_("Error opening file"),
|
||||
wxOK | wxCENTRE, &window);
|
||||
return;
|
||||
}
|
||||
int numRead = ff.Read(buf, 15);
|
||||
if (numRead != 15) {
|
||||
AudacityMessageBox(wxString::Format(_("File may be invalid or corrupted: \n%s"),
|
||||
fileName), _("Error Opening File or Project"),
|
||||
wxOK | wxCENTRE, &window);
|
||||
ff.Close();
|
||||
return;
|
||||
}
|
||||
buf[15] = 0;
|
||||
}
|
||||
|
||||
wxString temp = LAT1CTOWX(buf);
|
||||
|
||||
if (temp == wxT("AudacityProject")) {
|
||||
// It's an Audacity 1.0 (or earlier) project file.
|
||||
// If they bail out, return and do no more.
|
||||
if( !projectFileIO.WarnOfLegacyFile() )
|
||||
return;
|
||||
// Convert to the NEW format.
|
||||
bool success = ConvertLegacyProjectFile(wxFileName{ fileName });
|
||||
if (!success) {
|
||||
AudacityMessageBox(_("Audacity was unable to convert an Audacity 1.0 project to the new project format."),
|
||||
_("Error Opening Project"),
|
||||
wxOK | wxCENTRE, &window);
|
||||
return;
|
||||
}
|
||||
else {
|
||||
temp = wxT("<?xml ");
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: //v Surely we could be smarter about this, like checking much earlier that this is a .aup file.
|
||||
if (temp.Mid(0, 6) != wxT("<?xml ")) {
|
||||
// If it's not XML, try opening it as any other form of audio
|
||||
|
||||
#ifdef EXPERIMENTAL_DRAG_DROP_PLUG_INS
|
||||
// Is it a plug-in?
|
||||
if (PluginManager::Get().DropFile(fileName)) {
|
||||
MenuCreator::RebuildAllMenuBars();
|
||||
}
|
||||
else
|
||||
// No, so import.
|
||||
#endif
|
||||
|
||||
{
|
||||
#ifdef USE_MIDI
|
||||
if (FileNames::IsMidi(fileName))
|
||||
FileActions::DoImportMIDI( &project, fileName );
|
||||
else
|
||||
#endif
|
||||
Import( fileName );
|
||||
|
||||
window.ZoomAfterImport(nullptr);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// The handlers may be created during ReadProjectFile and are not needed
|
||||
// after this function exits.
|
||||
auto cleanupHandlers = finally( [this]{
|
||||
mImportXMLTagHandler.reset();
|
||||
mRecordingRecoveryHandler.reset();
|
||||
} );
|
||||
|
||||
auto results = ReadProjectFile( fileName );
|
||||
|
||||
if ( results.decodeError )
|
||||
return;
|
||||
|
||||
const bool bParseSuccess = results.parseSuccess;
|
||||
const wxString &errorStr = results.errorString;
|
||||
const bool err = results.trackError;
|
||||
|
||||
if (bParseSuccess) {
|
||||
auto &settings = ProjectSettings::Get( project );
|
||||
window.mbInitializingScrollbar = true; // this must precede AS_SetSnapTo
|
||||
// to make persistence of the vertical scrollbar position work
|
||||
|
||||
auto &selectionManager = ProjectSelectionManager::Get( project );
|
||||
selectionManager.AS_SetSnapTo(settings.GetSnapTo());
|
||||
selectionManager.AS_SetSelectionFormat(settings.GetSelectionFormat());
|
||||
selectionManager.SSBL_SetFrequencySelectionFormatName(
|
||||
settings.GetFrequencySelectionFormatName());
|
||||
selectionManager.SSBL_SetBandwidthSelectionFormatName(
|
||||
settings.GetBandwidthSelectionFormatName());
|
||||
|
||||
SelectionBar::Get( project ).SetRate( settings.GetRate() );
|
||||
|
||||
ProjectHistory::Get( project ).InitialState();
|
||||
trackPanel.SetFocusedTrack( *tracks.Any().begin() );
|
||||
window.HandleResize();
|
||||
trackPanel.Refresh(false);
|
||||
trackPanel.Update(); // force any repaint to happen now,
|
||||
// else any asynch calls into the blockfile code will not have
|
||||
// finished logging errors (if any) before the call to ProjectFSCK()
|
||||
|
||||
if (addtohistory)
|
||||
FileHistory::Global().AddFileToHistory(fileName);
|
||||
}
|
||||
|
||||
// Use a finally block here, because there are calls to Save() below which
|
||||
// might throw.
|
||||
bool closed = false;
|
||||
auto cleanup = finally( [&] {
|
||||
//release the flag.
|
||||
ODManager::UnmarkLoadedODFlag();
|
||||
|
||||
if (! closed ) {
|
||||
if ( bParseSuccess ) {
|
||||
// This is a no-fail:
|
||||
dirManager.FillBlockfilesCache();
|
||||
EnqueueODTasks();
|
||||
}
|
||||
|
||||
// For an unknown reason, OSX requires that the project window be
|
||||
// raised if a recovery took place.
|
||||
window.CallAfter( [&] { window.Raise(); } );
|
||||
}
|
||||
} );
|
||||
|
||||
if (bParseSuccess) {
|
||||
bool saved = false;
|
||||
|
||||
if (projectFileIO.IsRecovered())
|
||||
{
|
||||
// This project has been recovered, so write a NEW auto-save file
|
||||
// now and then DELETE the old one in the auto-save folder. Note that
|
||||
// at this point mFileName != fileName, because when opening a
|
||||
// recovered file mFileName is faked to point to the original file
|
||||
// which has been recovered, not the one in the auto-save folder.
|
||||
::ProjectFSCK(dirManager, err, true); // Correct problems in auto-recover mode.
|
||||
|
||||
// PushState calls AutoSave(), so no longer need to do so here.
|
||||
history.PushState(_("Project was recovered"), _("Recover"));
|
||||
|
||||
if (!wxRemoveFile(fileName))
|
||||
AudacityMessageBox(_("Could not remove old auto save file"),
|
||||
_("Error"), wxICON_STOP, &window);
|
||||
}
|
||||
else
|
||||
{
|
||||
// This is a regular project, check it and ask user
|
||||
int status = ::ProjectFSCK(dirManager, err, false);
|
||||
if (status & FSCKstatus_CLOSE_REQ)
|
||||
{
|
||||
// Vaughan, 2010-08-23: Note this did not do a real close.
|
||||
// It could cause problems if you get this, say on missing alias files,
|
||||
// then try to open a project with, e.g., missing blockfiles.
|
||||
// It then failed in SetProject, saying it cannot find the files,
|
||||
// then never go through ProjectFSCK to give more info.
|
||||
// Going through OnClose() may be overkill, but it's safe.
|
||||
/*
|
||||
// There was an error in the load/check and the user
|
||||
// explictly opted to close the project.
|
||||
mTracks->Clear(true);
|
||||
mFileName = wxT("");
|
||||
SetProjectTitle();
|
||||
mTrackPanel->Refresh(true);
|
||||
*/
|
||||
closed = true;
|
||||
SetMenuClose(true);
|
||||
window.Close();
|
||||
return;
|
||||
}
|
||||
else if (status & FSCKstatus_CHANGED)
|
||||
{
|
||||
// Mark the wave tracks as changed and redraw.
|
||||
for ( auto wt : tracks.Any<WaveTrack>() )
|
||||
// Only wave tracks have a notion of "changed".
|
||||
for (const auto &clip: wt->GetClips())
|
||||
clip->MarkChanged();
|
||||
|
||||
trackPanel.Refresh(true);
|
||||
|
||||
// Vaughan, 2010-08-20: This was bogus, as all the actions in ProjectFSCK
|
||||
// that return FSCKstatus_CHANGED cannot be undone.
|
||||
// this->PushState(_("Project checker repaired file"), _("Project Repair"));
|
||||
|
||||
if (status & FSCKstatus_SAVE_AUP)
|
||||
Save(), saved = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (mImportXMLTagHandler) {
|
||||
if (!saved)
|
||||
// We processed an <import> tag, so save it as a normal project,
|
||||
// with no <import> tags.
|
||||
Save();
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Vaughan, 2011-10-30:
|
||||
// See first topic at http://bugzilla.audacityteam.org/show_bug.cgi?id=451#c16.
|
||||
// Calling mTracks->Clear() with deleteTracks true results in data loss.
|
||||
|
||||
// PRL 2014-12-19:
|
||||
// I made many changes for wave track memory management, but only now
|
||||
// read the above comment. I may have invalidated the fix above (which
|
||||
// may have spared the files at the expense of leaked memory). But
|
||||
// here is a better way to accomplish the intent, doing like what happens
|
||||
// when the project closes:
|
||||
for ( auto pTrack : tracks.Any< WaveTrack >() )
|
||||
pTrack->CloseLock();
|
||||
|
||||
tracks.Clear(); //tracks.Clear(true);
|
||||
|
||||
project.SetFileName( wxT("") );
|
||||
projectFileIO.SetProjectTitle();
|
||||
|
||||
wxLogError(wxT("Could not parse file \"%s\". \nError: %s"), fileName, errorStr);
|
||||
|
||||
wxString url = wxT("FAQ:Errors_on_opening_or_recovering_an_Audacity_project");
|
||||
|
||||
// Certain errors have dedicated help.
|
||||
// On April-4th-2018, we did not request translation of the XML errors.
|
||||
// If/when we do, we will need _() around the comparison strings.
|
||||
if( errorStr.Contains( ("not well-formed (invalid token)") ) )
|
||||
url = "Error:_not_well-formed_(invalid_token)_at_line_x";
|
||||
else if( errorStr.Contains(("reference to invalid character number") ))
|
||||
url = "Error_Opening_Project:_Reference_to_invalid_character_number_at_line_x";
|
||||
else if( errorStr.Contains(("mismatched tag") ))
|
||||
url += "#mismatched";
|
||||
|
||||
// These two errors with FAQ entries are reported elsewhere, not here....
|
||||
//#[[#import-error|Error Importing: Aup is an Audacity Project file. Use the File > Open command]]
|
||||
//#[[#corrupt|Error Opening File or Project: File may be invalid or corrupted]]
|
||||
|
||||
// If we did want to handle every single parse error, these are they....
|
||||
/*
|
||||
XML_L("out of memory"),
|
||||
XML_L("syntax error"),
|
||||
XML_L("no element found"),
|
||||
XML_L("not well-formed (invalid token)"),
|
||||
XML_L("unclosed token"),
|
||||
XML_L("partial character"),
|
||||
XML_L("mismatched tag"),
|
||||
XML_L("duplicate attribute"),
|
||||
XML_L("junk after document element"),
|
||||
XML_L("illegal parameter entity reference"),
|
||||
XML_L("undefined entity"),
|
||||
XML_L("recursive entity reference"),
|
||||
XML_L("asynchronous entity"),
|
||||
XML_L("reference to invalid character number"),
|
||||
XML_L("reference to binary entity"),
|
||||
XML_L("reference to external entity in attribute"),
|
||||
XML_L("XML or text declaration not at start of entity"),
|
||||
XML_L("unknown encoding"),
|
||||
XML_L("encoding specified in XML declaration is incorrect"),
|
||||
XML_L("unclosed CDATA section"),
|
||||
XML_L("error in processing external entity reference"),
|
||||
XML_L("document is not standalone"),
|
||||
XML_L("unexpected parser state - please send a bug report"),
|
||||
XML_L("entity declared in parameter entity"),
|
||||
XML_L("requested feature requires XML_DTD support in Expat"),
|
||||
XML_L("cannot change setting once parsing has begun"),
|
||||
XML_L("unbound prefix"),
|
||||
XML_L("must not undeclare prefix"),
|
||||
XML_L("incomplete markup in parameter entity"),
|
||||
XML_L("XML declaration not well-formed"),
|
||||
XML_L("text declaration not well-formed"),
|
||||
XML_L("illegal character(s) in public id"),
|
||||
XML_L("parser suspended"),
|
||||
XML_L("parser not suspended"),
|
||||
XML_L("parsing aborted"),
|
||||
XML_L("parsing finished"),
|
||||
XML_L("cannot suspend in external parameter entity"),
|
||||
XML_L("reserved prefix (xml) must not be undeclared or bound to another namespace name"),
|
||||
XML_L("reserved prefix (xmlns) must not be declared or undeclared"),
|
||||
XML_L("prefix must not be bound to one of the reserved namespace names")
|
||||
*/
|
||||
|
||||
ShowErrorDialog(
|
||||
&window,
|
||||
_("Error Opening Project"),
|
||||
errorStr,
|
||||
url);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector< std::shared_ptr< Track > >
|
||||
ProjectFileManager::AddImportedTracks(const FilePath &fileName,
|
||||
TrackHolders &&newTracks)
|
||||
{
|
||||
auto &project = mProject;
|
||||
auto &history = ProjectHistory::Get( project );
|
||||
auto &projectFileIO = ProjectFileIO::Get( project );
|
||||
auto &tracks = TrackList::Get( project );
|
||||
|
||||
std::vector< std::shared_ptr< Track > > results;
|
||||
|
||||
SelectActions::SelectNone( project );
|
||||
|
||||
bool initiallyEmpty = tracks.empty();
|
||||
double newRate = 0;
|
||||
wxString trackNameBase = fileName.AfterLast(wxFILE_SEP_PATH).BeforeLast('.');
|
||||
int i = -1;
|
||||
|
||||
// Must add all tracks first (before using Track::IsLeader)
|
||||
for (auto &group : newTracks) {
|
||||
if (group.empty()) {
|
||||
wxASSERT(false);
|
||||
continue;
|
||||
}
|
||||
auto first = group.begin()->get();
|
||||
auto nChannels = group.size();
|
||||
for (auto &uNewTrack : group) {
|
||||
auto newTrack = tracks.Add( uNewTrack );
|
||||
results.push_back(newTrack->SharedPointer());
|
||||
}
|
||||
tracks.GroupChannels(*first, nChannels);
|
||||
}
|
||||
newTracks.clear();
|
||||
|
||||
// Now name them
|
||||
|
||||
// Add numbers to track names only if there is more than one (mono or stereo)
|
||||
// track (not necessarily, more than one channel)
|
||||
const bool useSuffix =
|
||||
make_iterator_range( results.begin() + 1, results.end() )
|
||||
.any_of( []( decltype(*results.begin()) &pTrack )
|
||||
{ return pTrack->IsLeader(); } );
|
||||
|
||||
for (const auto &newTrack : results) {
|
||||
if ( newTrack->IsLeader() )
|
||||
// Count groups only
|
||||
++i;
|
||||
|
||||
newTrack->SetSelected(true);
|
||||
|
||||
if ( useSuffix )
|
||||
newTrack->SetName(trackNameBase + wxString::Format(wxT(" %d" ), i + 1));
|
||||
else
|
||||
newTrack->SetName(trackNameBase);
|
||||
|
||||
newTrack->TypeSwitch( [&](WaveTrack *wt) {
|
||||
if (newRate == 0)
|
||||
newRate = wt->GetRate();
|
||||
|
||||
// Check if NEW track contains aliased blockfiles and if yes,
|
||||
// remember this to show a warning later
|
||||
if(WaveClip* clip = wt->GetClipByIndex(0)) {
|
||||
BlockArray &blocks = clip->GetSequence()->GetBlockArray();
|
||||
if (blocks.size())
|
||||
{
|
||||
SeqBlock& block = blocks[0];
|
||||
if (block.f->IsAlias())
|
||||
SetImportedDependencies( true );
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Automatically assign rate of imported file to whole project,
|
||||
// if this is the first file that is imported
|
||||
if (initiallyEmpty && newRate > 0) {
|
||||
auto &settings = ProjectSettings::Get( project );
|
||||
settings.SetRate( newRate );
|
||||
SelectionBar::Get( project ).SetRate( newRate );
|
||||
}
|
||||
|
||||
history.PushState(wxString::Format(_("Imported '%s'"), fileName),
|
||||
_("Import"));
|
||||
|
||||
#if defined(__WXGTK__)
|
||||
// See bug #1224
|
||||
// The track panel hasn't we been fully created, so the DoZoomFit() will not give
|
||||
// expected results due to a window width of zero. Should be safe to yield here to
|
||||
// allow the creattion to complete. If this becomes a problem, it "might" be possible
|
||||
// to queue a dummy event to trigger the DoZoomFit().
|
||||
wxEventLoopBase::GetActive()->YieldFor(wxEVT_CATEGORY_UI | wxEVT_CATEGORY_USER_INPUT);
|
||||
#endif
|
||||
|
||||
if (initiallyEmpty && !projectFileIO.IsProjectSaved() ) {
|
||||
wxString name = fileName.AfterLast(wxFILE_SEP_PATH).BeforeLast(wxT('.'));
|
||||
project.SetFileName(
|
||||
::wxPathOnly(fileName) + wxFILE_SEP_PATH + name + wxT(".aup") );
|
||||
projectFileIO.SetLoadedFromAup( false );
|
||||
projectFileIO.SetProjectTitle();
|
||||
}
|
||||
|
||||
// Moved this call to higher levels to prevent flicker redrawing everything on each file.
|
||||
// HandleResize();
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
// If pNewTrackList is passed in non-NULL, it gets filled with the pointers to NEW tracks.
|
||||
bool ProjectFileManager::Import(
|
||||
const FilePath &fileName, WaveTrackArray* pTrackArray /*= NULL*/)
|
||||
{
|
||||
auto &project = mProject;
|
||||
auto &dirManager = DirManager::Get( project );
|
||||
auto oldTags = Tags::Get( project ).shared_from_this();
|
||||
TrackHolders newTracks;
|
||||
wxString errorMessage;
|
||||
|
||||
{
|
||||
// Backup Tags, before the import. Be prepared to roll back changes.
|
||||
bool committed = false;
|
||||
auto cleanup = finally([&]{
|
||||
if ( !committed )
|
||||
Tags::Set( project, oldTags );
|
||||
});
|
||||
auto newTags = oldTags->Duplicate();
|
||||
Tags::Set( project, newTags );
|
||||
|
||||
bool success = Importer::Get().Import(fileName,
|
||||
&TrackFactory::Get( project ),
|
||||
newTracks,
|
||||
newTags.get(),
|
||||
errorMessage);
|
||||
|
||||
if (!errorMessage.empty()) {
|
||||
// Error message derived from Importer::Import
|
||||
// Additional help via a Help button links to the manual.
|
||||
ShowErrorDialog(&GetProjectFrame( project ), _("Error Importing"),
|
||||
errorMessage, wxT("Importing_Audio"));
|
||||
}
|
||||
if (!success)
|
||||
return false;
|
||||
|
||||
FileHistory::Global().AddFileToHistory(fileName);
|
||||
|
||||
// no more errors, commit
|
||||
committed = true;
|
||||
}
|
||||
|
||||
// for LOF ("list of files") files, do not import the file as if it
|
||||
// were an audio file itself
|
||||
if (fileName.AfterLast('.').IsSameAs(wxT("lof"), false)) {
|
||||
// PRL: don't redundantly do the steps below, because we already
|
||||
// did it in case of LOF, because of some weird recursion back to this
|
||||
// same function. I think this should be untangled.
|
||||
|
||||
// So Undo history push is not bypassed, despite appearances.
|
||||
return false;
|
||||
}
|
||||
|
||||
// PRL: Undo history is incremented inside this:
|
||||
auto newSharedTracks = AddImportedTracks(fileName, std::move(newTracks));
|
||||
|
||||
if (pTrackArray) {
|
||||
for (const auto &newTrack : newSharedTracks) {
|
||||
newTrack->TypeSwitch( [&](WaveTrack *wt) {
|
||||
pTrackArray->push_back( wt->SharedPointer< WaveTrack >() );
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
int mode = gPrefs->Read(wxT("/AudioFiles/NormalizeOnLoad"), 0L);
|
||||
if (mode == 1) {
|
||||
//TODO: All we want is a SelectAll()
|
||||
SelectActions::SelectNone( project );
|
||||
SelectActions::SelectAllIfNone( project );
|
||||
const CommandContext context( project );
|
||||
PluginActions::DoEffect(
|
||||
EffectManager::Get().GetEffectByIdentifier(wxT("Normalize")),
|
||||
context,
|
||||
PluginActions::kConfigured);
|
||||
}
|
||||
|
||||
// This is a no-fail:
|
||||
dirManager.FillBlockfilesCache();
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -12,13 +12,23 @@ Paul Licameli split from AudacityProject.h
|
|||
#define __AUDACITY_PROJECT_FILE_MANAGER__
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "ClientData.h" // to inherit
|
||||
#include "import/ImportRaw.h" // defines TrackHolders
|
||||
|
||||
class wxString;
|
||||
class wxFileName;
|
||||
class AudacityProject;
|
||||
class ImportXMLTagHandler;
|
||||
class RecordingRecoveryHandler;
|
||||
class Track;
|
||||
class TrackList;
|
||||
class WaveTrack;
|
||||
class XMLTagHandler;
|
||||
namespace ProjectFileIORegistry{ struct Entry; }
|
||||
|
||||
using WaveTrackArray = std::vector < std::shared_ptr < WaveTrack > >;
|
||||
|
||||
class ProjectFileManager final
|
||||
: public ClientData::Base
|
||||
|
@ -54,20 +64,79 @@ public:
|
|||
|
||||
void Reset();
|
||||
|
||||
void SetImportedDependencies( bool value ) { mImportedDependencies = value; }
|
||||
/** @brief Show an open dialogue for opening audio files, and possibly other
|
||||
* sorts of files.
|
||||
*
|
||||
* The file type filter will automatically contain:
|
||||
* - "All files" with any extension or none,
|
||||
* - "All supported files" based on the file formats supported in this
|
||||
* build of Audacity,
|
||||
* - All of the individual formats specified by the importer plug-ins which
|
||||
* are built into this build of Audacity, each with the relevant file
|
||||
* extensions for that format.
|
||||
* The dialogue will start in the DefaultOpenPath directory read from the
|
||||
* preferences, failing that the working directory. The file format filter
|
||||
* will be set to the DefaultOpenType from the preferences, failing that
|
||||
* the first format specified in the dialogue. These two parameters will
|
||||
* be saved to the preferences once the user has chosen a file to open.
|
||||
* @param extraformat Specify the name of an additional format to allow
|
||||
* opening in this dialogue. This string is free-form, but should be short
|
||||
* enough to fit in the file dialogue filter drop-down. It should be
|
||||
* translated.
|
||||
* @param extrafilter Specify the file extension(s) for the additional format
|
||||
* specified by extraformat. The patterns must include the wildcard (e.g.
|
||||
* "*.aup" not "aup" or ".aup"), separate multiple patters with a semicolon,
|
||||
* e.g. "*.aup;*.AUP" because patterns are case-sensitive. Do not add a
|
||||
* trailing semicolon to the string. This string should not be translated
|
||||
* @return Array of file paths which the user selected to open (multiple
|
||||
* selections allowed).
|
||||
*/
|
||||
static wxArrayString ShowOpenDialog(const wxString &extraformat = {},
|
||||
const wxString &extrafilter = {});
|
||||
|
||||
private:
|
||||
static bool IsAlreadyOpen(const FilePath &projPathName);
|
||||
|
||||
void OpenFile(const FilePath &fileName, bool addtohistory = true);
|
||||
|
||||
// If pNewTrackList is passed in non-NULL, it gets filled with the pointers to NEW tracks.
|
||||
bool Import(const FilePath &fileName, WaveTrackArray *pTrackArray = NULL);
|
||||
|
||||
// Takes array of unique pointers; returns array of shared
|
||||
std::vector< std::shared_ptr<Track> >
|
||||
AddImportedTracks(const FilePath &fileName,
|
||||
TrackHolders &&newTracks);
|
||||
|
||||
bool GetMenuClose() const { return mMenuClose; }
|
||||
void SetMenuClose(bool value) { mMenuClose = value; }
|
||||
|
||||
private:
|
||||
void SetImportedDependencies( bool value ) { mImportedDependencies = value; }
|
||||
|
||||
// Push names of NEW export files onto the path list
|
||||
bool SaveCopyWaveTracks(const FilePath & strProjectPathName,
|
||||
bool bLossless, FilePaths &strOtherNamesArray);
|
||||
bool DoSave(bool fromSaveAs, bool bWantSaveCopy, bool bLossless = false);
|
||||
|
||||
// Declared in this class so that they can have access to private members
|
||||
static XMLTagHandler *RecordingRecoveryFactory( AudacityProject &project );
|
||||
static ProjectFileIORegistry::Entry sRecoveryFactory;
|
||||
static XMLTagHandler *ImportHandlerFactory( AudacityProject &project );
|
||||
static ProjectFileIORegistry::Entry sImportHandlerFactory;
|
||||
|
||||
AudacityProject &mProject;
|
||||
|
||||
std::shared_ptr<TrackList> mLastSavedTracks;
|
||||
|
||||
// The handler that handles recovery of <recordingrecovery> tags
|
||||
std::unique_ptr<RecordingRecoveryHandler> mRecordingRecoveryHandler;
|
||||
|
||||
std::unique_ptr<ImportXMLTagHandler> mImportXMLTagHandler;
|
||||
|
||||
// Dependencies have been imported and a warning should be shown on save
|
||||
bool mImportedDependencies{ false };
|
||||
|
||||
// Are we currently closing as the result of a menu command?
|
||||
bool mMenuClose{ false };
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -19,32 +19,22 @@ Paul Licameli split from AudacityProject.cpp
|
|||
#include "Clipboard.h"
|
||||
#include "DirManager.h"
|
||||
#include "FileNames.h"
|
||||
#include "Legacy.h"
|
||||
#include "Menus.h"
|
||||
#include "MissingAliasFileDialog.h"
|
||||
#include "ModuleManager.h"
|
||||
#include "PlatformCompatibility.h"
|
||||
#include "Project.h"
|
||||
#include "ProjectAudioIO.h"
|
||||
#include "ProjectAudioManager.h"
|
||||
#include "ProjectFileIO.h"
|
||||
#include "ProjectFileIORegistry.h"
|
||||
#include "ProjectFileManager.h"
|
||||
#include "ProjectFSCK.h"
|
||||
#include "ProjectHistory.h"
|
||||
#include "ProjectSelectionManager.h"
|
||||
#include "ProjectSettings.h"
|
||||
#include "ProjectWindow.h"
|
||||
#include "Sequence.h"
|
||||
#include "Tags.h"
|
||||
#include "TrackPanel.h"
|
||||
#include "UndoManager.h"
|
||||
#include "WaveTrack.h"
|
||||
#include "WaveClip.h"
|
||||
#include "wxFileNameWrapper.h"
|
||||
#include "commands/CommandContext.h"
|
||||
#include "effects/EffectManager.h"
|
||||
#include "import/Import.h"
|
||||
#include "ondemand/ODManager.h"
|
||||
#include "prefs/QualityPrefs.h"
|
||||
#include "toolbars/ControlToolBar.h"
|
||||
|
@ -58,7 +48,6 @@ Paul Licameli split from AudacityProject.cpp
|
|||
|
||||
#include <wx/dataobj.h>
|
||||
#include <wx/dnd.h>
|
||||
#include <wx/evtloop.h>
|
||||
|
||||
const int AudacityProjectTimerID = 5200;
|
||||
|
||||
|
@ -338,7 +327,7 @@ public:
|
|||
FileActions::DoImportMIDI(mProject, name);
|
||||
else
|
||||
#endif
|
||||
ProjectManager::Get( *mProject ).Import(name);
|
||||
ProjectFileManager::Get( *mProject ).Import(name);
|
||||
}
|
||||
|
||||
auto &window = ProjectWindow::Get( *mProject );
|
||||
|
@ -539,7 +528,7 @@ void ProjectManager::OnCloseWindow(wxCloseEvent & event)
|
|||
#ifdef __WXMAC__
|
||||
quitOnClose = false;
|
||||
#else
|
||||
quitOnClose = !mMenuClose;
|
||||
quitOnClose = !projectFileManager.GetMenuClose();
|
||||
#endif
|
||||
|
||||
// DanH: If we're definitely about to quit, clear the clipboard.
|
||||
|
@ -645,136 +634,11 @@ void ProjectManager::OnOpenAudioFile(wxCommandEvent & event)
|
|||
const wxString &cmd = event.GetString();
|
||||
|
||||
if (!cmd.empty())
|
||||
OpenFile(cmd);
|
||||
ProjectFileManager::Get( mProject ).OpenFile(cmd);
|
||||
|
||||
window.RequestUserAttention();
|
||||
}
|
||||
|
||||
// static method, can be called outside of a project
|
||||
wxArrayString ProjectManager::ShowOpenDialog(const wxString &extraformat, const wxString &extrafilter)
|
||||
{
|
||||
FormatList l;
|
||||
wxString filter; ///< List of file format names and extensions, separated
|
||||
/// by | characters between _formats_ and extensions for each _format_, i.e.
|
||||
/// format1name | *.ext | format2name | *.ex1;*.ex2
|
||||
wxString all; ///< One long list of all supported file extensions,
|
||||
/// semicolon separated
|
||||
|
||||
if (!extraformat.empty())
|
||||
{ // additional format specified
|
||||
all = extrafilter + wxT(';');
|
||||
// add it to the "all supported files" filter string
|
||||
}
|
||||
|
||||
// Construct the filter
|
||||
Importer::Get().GetSupportedImportFormats(&l);
|
||||
|
||||
for (const auto &format : l) {
|
||||
/* this loop runs once per supported _format_ */
|
||||
const Format *f = &format;
|
||||
|
||||
wxString newfilter = f->formatName + wxT("|");
|
||||
// bung format name into string plus | separator
|
||||
for (size_t i = 0; i < f->formatExtensions.size(); i++) {
|
||||
/* this loop runs once per valid _file extension_ for file containing
|
||||
* the current _format_ */
|
||||
if (!newfilter.Contains(wxT("*.") + f->formatExtensions[i] + wxT(";")))
|
||||
newfilter += wxT("*.") + f->formatExtensions[i] + wxT(";");
|
||||
if (!all.Contains(wxT("*.") + f->formatExtensions[i] + wxT(";")))
|
||||
all += wxT("*.") + f->formatExtensions[i] + wxT(";");
|
||||
}
|
||||
newfilter.RemoveLast(1);
|
||||
filter += newfilter;
|
||||
filter += wxT("|");
|
||||
}
|
||||
all.RemoveLast(1);
|
||||
filter.RemoveLast(1);
|
||||
|
||||
// For testing long filters
|
||||
#if 0
|
||||
wxString test = wxT("*.aaa;*.bbb;*.ccc;*.ddd;*.eee");
|
||||
all = test + wxT(';') + test + wxT(';') + test + wxT(';') +
|
||||
test + wxT(';') + test + wxT(';') + test + wxT(';') +
|
||||
test + wxT(';') + test + wxT(';') + test + wxT(';') +
|
||||
all;
|
||||
#endif
|
||||
|
||||
/* i18n-hint: The vertical bars and * are essential here.*/
|
||||
wxString mask = _("All files|*|All supported files|") +
|
||||
all + wxT("|"); // "all" and "all supported" entries
|
||||
if (!extraformat.empty())
|
||||
{ // append caller-defined format if supplied
|
||||
mask += extraformat + wxT("|") + extrafilter + wxT("|");
|
||||
}
|
||||
mask += filter; // put the names and extensions of all the importer formats
|
||||
// we built up earlier into the mask
|
||||
|
||||
// Retrieve saved path and type
|
||||
auto path = FileNames::FindDefaultPath(FileNames::Operation::Open);
|
||||
wxString type = gPrefs->Read(wxT("/DefaultOpenType"),mask.BeforeFirst(wxT('|')));
|
||||
|
||||
// Convert the type to the filter index
|
||||
int index = mask.First(type + wxT("|"));
|
||||
if (index == wxNOT_FOUND) {
|
||||
index = 0;
|
||||
}
|
||||
else {
|
||||
index = mask.Left(index).Freq(wxT('|')) / 2;
|
||||
if (index < 0) {
|
||||
index = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Construct and display the file dialog
|
||||
wxArrayString selected;
|
||||
|
||||
FileDialogWrapper dlog(NULL,
|
||||
_("Select one or more files"),
|
||||
path,
|
||||
wxT(""),
|
||||
mask,
|
||||
wxFD_OPEN | wxFD_MULTIPLE | wxRESIZE_BORDER);
|
||||
|
||||
dlog.SetFilterIndex(index);
|
||||
|
||||
int dialogResult = dlog.ShowModal();
|
||||
|
||||
// Convert the filter index to type and save
|
||||
index = dlog.GetFilterIndex();
|
||||
for (int i = 0; i < index; i++) {
|
||||
mask = mask.AfterFirst(wxT('|')).AfterFirst(wxT('|'));
|
||||
}
|
||||
gPrefs->Write(wxT("/DefaultOpenType"), mask.BeforeFirst(wxT('|')));
|
||||
gPrefs->Write(wxT("/LastOpenType"), mask.BeforeFirst(wxT('|')));
|
||||
gPrefs->Flush();
|
||||
|
||||
if (dialogResult == wxID_OK) {
|
||||
// Return the selected files
|
||||
dlog.GetPaths(selected);
|
||||
}
|
||||
return selected;
|
||||
}
|
||||
|
||||
// static method, can be called outside of a project
|
||||
bool ProjectManager::IsAlreadyOpen(const FilePath &projPathName)
|
||||
{
|
||||
const wxFileName newProjPathName(projPathName);
|
||||
auto start = AllProjects{}.begin(), finish = AllProjects{}.end(),
|
||||
iter = std::find_if( start, finish,
|
||||
[&]( const AllProjects::value_type &ptr ){
|
||||
return newProjPathName.SameAs(wxFileNameWrapper{ ptr->GetFileName() });
|
||||
} );
|
||||
if (iter != finish) {
|
||||
wxString errMsg =
|
||||
wxString::Format(_("%s is already open in another window."),
|
||||
newProjPathName.GetName());
|
||||
wxLogError(errMsg);
|
||||
AudacityMessageBox(errMsg, _("Error Opening Project"), wxOK | wxCENTRE);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// static method, can be called outside of a project
|
||||
void ProjectManager::OpenFiles(AudacityProject *proj)
|
||||
{
|
||||
|
@ -783,7 +647,7 @@ void ProjectManager::OpenFiles(AudacityProject *proj)
|
|||
* with Audacity. Do not include pipe symbols or .aup (this extension will
|
||||
* now be added automatically for the Save Projects dialogues).*/
|
||||
auto selectedFiles =
|
||||
ProjectManager::ShowOpenDialog(_("Audacity projects"), wxT("*.aup"));
|
||||
ProjectFileManager::ShowOpenDialog(_("Audacity projects"), wxT("*.aup"));
|
||||
if (selectedFiles.size() == 0) {
|
||||
gPrefs->Write(wxT("/LastOpenType"),wxT(""));
|
||||
gPrefs->Flush();
|
||||
|
@ -805,7 +669,7 @@ void ProjectManager::OpenFiles(AudacityProject *proj)
|
|||
const wxString &fileName = selectedFiles[ff];
|
||||
|
||||
// Make sure it isn't already open.
|
||||
if (IsAlreadyOpen(fileName))
|
||||
if (ProjectFileManager::IsAlreadyOpen(fileName))
|
||||
continue; // Skip ones that are already open.
|
||||
|
||||
FileNames::UpdateDefaultPath(FileNames::Operation::Open, fileName);
|
||||
|
@ -843,7 +707,7 @@ AudacityProject *ProjectManager::OpenProject(
|
|||
if( pNewProject )
|
||||
GetProjectFrame( *pNewProject ).Close(true);
|
||||
} );
|
||||
Get( *pProject ).OpenFile( fileNameArg, addtohistory );
|
||||
ProjectFileManager::Get( *pProject ).OpenFile( fileNameArg, addtohistory );
|
||||
pNewProject = nullptr;
|
||||
auto &projectFileIO = ProjectFileIO::Get( *pProject );
|
||||
if( projectFileIO.IsRecovered() )
|
||||
|
@ -853,648 +717,6 @@ AudacityProject *ProjectManager::OpenProject(
|
|||
return pProject;
|
||||
}
|
||||
|
||||
XMLTagHandler *
|
||||
ProjectManager::RecordingRecoveryFactory( AudacityProject &project ) {
|
||||
auto &projectManager = Get( project );
|
||||
auto &ptr = projectManager.mRecordingRecoveryHandler;
|
||||
if (!ptr)
|
||||
ptr =
|
||||
std::make_unique<RecordingRecoveryHandler>( &project );
|
||||
return ptr.get();
|
||||
}
|
||||
|
||||
ProjectFileIORegistry::Entry
|
||||
ProjectManager::sRecoveryFactory{
|
||||
wxT("recordingrecovery"), RecordingRecoveryFactory
|
||||
};
|
||||
|
||||
// XML handler for <import> tag
|
||||
class ImportXMLTagHandler final : public XMLTagHandler
|
||||
{
|
||||
public:
|
||||
ImportXMLTagHandler(AudacityProject* pProject) { mProject = pProject; }
|
||||
|
||||
bool HandleXMLTag(const wxChar *tag, const wxChar **attrs) override;
|
||||
XMLTagHandler *HandleXMLChild(const wxChar * WXUNUSED(tag)) override
|
||||
{ return NULL; }
|
||||
|
||||
// Don't want a WriteXML method because ImportXMLTagHandler is not a WaveTrack.
|
||||
// <import> tags are instead written by AudacityProject::WriteXML.
|
||||
// void WriteXML(XMLWriter &xmlFile) /* not override */ { wxASSERT(false); }
|
||||
|
||||
private:
|
||||
AudacityProject* mProject;
|
||||
};
|
||||
|
||||
bool ImportXMLTagHandler::HandleXMLTag(const wxChar *tag, const wxChar **attrs)
|
||||
{
|
||||
if (wxStrcmp(tag, wxT("import")) || attrs==NULL || (*attrs)==NULL || wxStrcmp(*attrs++, wxT("filename")))
|
||||
return false;
|
||||
wxString strAttr = *attrs;
|
||||
if (!XMLValueChecker::IsGoodPathName(strAttr))
|
||||
{
|
||||
// Maybe strAttr is just a fileName, not the full path. Try the project data directory.
|
||||
wxFileNameWrapper fileName{
|
||||
DirManager::Get( *mProject ).GetProjectDataDir(), strAttr };
|
||||
if (XMLValueChecker::IsGoodFileName(strAttr, fileName.GetPath(wxPATH_GET_VOLUME)))
|
||||
strAttr = fileName.GetFullPath();
|
||||
else
|
||||
{
|
||||
wxLogWarning(wxT("Could not import file: %s"), strAttr);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
WaveTrackArray trackArray;
|
||||
|
||||
// Guard this call so that C++ exceptions don't propagate through
|
||||
// the expat library
|
||||
GuardedCall(
|
||||
[&] {
|
||||
ProjectManager::Get( *mProject ).Import(strAttr, &trackArray); },
|
||||
[&] (AudacityException*) { trackArray.clear(); }
|
||||
);
|
||||
|
||||
if (trackArray.empty())
|
||||
return false;
|
||||
|
||||
// Handle other attributes, now that we have the tracks.
|
||||
attrs++;
|
||||
const wxChar** pAttr;
|
||||
bool bSuccess = true;
|
||||
|
||||
for (size_t i = 0; i < trackArray.size(); i++)
|
||||
{
|
||||
// Most of the "import" tag attributes are the same as for "wavetrack" tags,
|
||||
// so apply them via WaveTrack::HandleXMLTag().
|
||||
bSuccess = trackArray[i]->HandleXMLTag(wxT("wavetrack"), attrs);
|
||||
|
||||
// "offset" tag is ignored in WaveTrack::HandleXMLTag except for legacy projects,
|
||||
// so handle it here.
|
||||
double dblValue;
|
||||
pAttr = attrs;
|
||||
while (*pAttr)
|
||||
{
|
||||
const wxChar *attr = *pAttr++;
|
||||
const wxChar *value = *pAttr++;
|
||||
const wxString strValue = value;
|
||||
if (!wxStrcmp(attr, wxT("offset")) &&
|
||||
XMLValueChecker::IsGoodString(strValue) &&
|
||||
Internat::CompatibleToDouble(strValue, &dblValue))
|
||||
trackArray[i]->SetOffset(dblValue);
|
||||
}
|
||||
}
|
||||
return bSuccess;
|
||||
};
|
||||
|
||||
XMLTagHandler *
|
||||
ProjectManager::ImportHandlerFactory( AudacityProject &project ) {
|
||||
auto &projectManager = Get( project );
|
||||
auto &ptr = projectManager.mImportXMLTagHandler;
|
||||
if (!ptr)
|
||||
ptr =
|
||||
std::make_unique<ImportXMLTagHandler>( &project );
|
||||
return ptr.get();
|
||||
}
|
||||
|
||||
ProjectFileIORegistry::Entry
|
||||
ProjectManager::sImportHandlerFactory{
|
||||
wxT("import"), ImportHandlerFactory
|
||||
};
|
||||
|
||||
// FIXME:? TRAP_ERR This should return a result that is checked.
|
||||
// See comment in AudacityApp::MRUOpen().
|
||||
void ProjectManager::OpenFile(const FilePath &fileNameArg, bool addtohistory)
|
||||
{
|
||||
auto &project = mProject;
|
||||
auto &history = ProjectHistory::Get( project );
|
||||
auto &projectFileIO = ProjectFileIO::Get( project );
|
||||
auto &projectFileManager = ProjectFileManager::Get( project );
|
||||
auto &tracks = TrackList::Get( project );
|
||||
auto &trackPanel = TrackPanel::Get( project );
|
||||
auto &dirManager = DirManager::Get( project );
|
||||
auto &window = ProjectWindow::Get( project );
|
||||
|
||||
// On Win32, we may be given a short (DOS-compatible) file name on rare
|
||||
// occassions (e.g. stuff like "C:\PROGRA~1\AUDACI~1\PROJEC~1.AUP"). We
|
||||
// convert these to long file name first.
|
||||
auto fileName = PlatformCompatibility::ConvertSlashInFileName(
|
||||
PlatformCompatibility::GetLongFileName(fileNameArg));
|
||||
|
||||
// Make sure it isn't already open.
|
||||
// Vaughan, 2011-03-25: This was done previously in AudacityProject::OpenFiles()
|
||||
// and AudacityApp::MRUOpen(), but if you open an aup file by double-clicking it
|
||||
// from, e.g., Win Explorer, it would bypass those, get to here with no check,
|
||||
// then open a NEW project from the same data with no warning.
|
||||
// This was reported in http://bugzilla.audacityteam.org/show_bug.cgi?id=137#c17,
|
||||
// but is not really part of that bug. Anyway, prevent it!
|
||||
if (IsAlreadyOpen(fileName))
|
||||
return;
|
||||
|
||||
|
||||
// Data loss may occur if users mistakenly try to open ".aup.bak" files
|
||||
// left over from an unsuccessful save or by previous versions of Audacity.
|
||||
// So we always refuse to open such files.
|
||||
if (fileName.Lower().EndsWith(wxT(".aup.bak")))
|
||||
{
|
||||
AudacityMessageBox(
|
||||
_("You are trying to open an automatically created backup file.\nDoing this may result in severe data loss.\n\nPlease open the actual Audacity project file instead."),
|
||||
_("Warning - Backup File Detected"),
|
||||
wxOK | wxCENTRE, &window);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!::wxFileExists(fileName)) {
|
||||
AudacityMessageBox(
|
||||
wxString::Format( _("Could not open file: %s"), fileName ),
|
||||
("Error Opening File"),
|
||||
wxOK | wxCENTRE, &window);
|
||||
return;
|
||||
}
|
||||
|
||||
// We want to open projects using wxTextFile, but if it's NOT a project
|
||||
// file (but actually a WAV file, for example), then wxTextFile will spin
|
||||
// for a long time searching for line breaks. So, we look for our
|
||||
// signature at the beginning of the file first:
|
||||
|
||||
char buf[16];
|
||||
{
|
||||
wxFFile ff(fileName, wxT("rb"));
|
||||
if (!ff.IsOpened()) {
|
||||
AudacityMessageBox(
|
||||
wxString::Format( _("Could not open file: %s"), fileName ),
|
||||
_("Error opening file"),
|
||||
wxOK | wxCENTRE, &window);
|
||||
return;
|
||||
}
|
||||
int numRead = ff.Read(buf, 15);
|
||||
if (numRead != 15) {
|
||||
AudacityMessageBox(wxString::Format(_("File may be invalid or corrupted: \n%s"),
|
||||
fileName), _("Error Opening File or Project"),
|
||||
wxOK | wxCENTRE, &window);
|
||||
ff.Close();
|
||||
return;
|
||||
}
|
||||
buf[15] = 0;
|
||||
}
|
||||
|
||||
wxString temp = LAT1CTOWX(buf);
|
||||
|
||||
if (temp == wxT("AudacityProject")) {
|
||||
// It's an Audacity 1.0 (or earlier) project file.
|
||||
// If they bail out, return and do no more.
|
||||
if( !projectFileIO.WarnOfLegacyFile() )
|
||||
return;
|
||||
// Convert to the NEW format.
|
||||
bool success = ConvertLegacyProjectFile(wxFileName{ fileName });
|
||||
if (!success) {
|
||||
AudacityMessageBox(_("Audacity was unable to convert an Audacity 1.0 project to the new project format."),
|
||||
_("Error Opening Project"),
|
||||
wxOK | wxCENTRE, &window);
|
||||
return;
|
||||
}
|
||||
else {
|
||||
temp = wxT("<?xml ");
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: //v Surely we could be smarter about this, like checking much earlier that this is a .aup file.
|
||||
if (temp.Mid(0, 6) != wxT("<?xml ")) {
|
||||
// If it's not XML, try opening it as any other form of audio
|
||||
|
||||
#ifdef EXPERIMENTAL_DRAG_DROP_PLUG_INS
|
||||
// Is it a plug-in?
|
||||
if (PluginManager::Get().DropFile(fileName)) {
|
||||
MenuCreator::RebuildAllMenuBars();
|
||||
}
|
||||
else
|
||||
// No, so import.
|
||||
#endif
|
||||
|
||||
{
|
||||
#ifdef USE_MIDI
|
||||
if (FileNames::IsMidi(fileName))
|
||||
FileActions::DoImportMIDI( &project, fileName );
|
||||
else
|
||||
#endif
|
||||
Import( fileName );
|
||||
|
||||
window.ZoomAfterImport(nullptr);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// The handlers may be created during ReadProjectFile and are not needed
|
||||
// after this function exits.
|
||||
auto cleanupHandlers = finally( [this]{
|
||||
mImportXMLTagHandler.reset();
|
||||
mRecordingRecoveryHandler.reset();
|
||||
} );
|
||||
|
||||
auto results = projectFileManager.ReadProjectFile( fileName );
|
||||
|
||||
if ( results.decodeError )
|
||||
return;
|
||||
|
||||
const bool bParseSuccess = results.parseSuccess;
|
||||
const wxString &errorStr = results.errorString;
|
||||
const bool err = results.trackError;
|
||||
|
||||
if (bParseSuccess) {
|
||||
auto &settings = ProjectSettings::Get( project );
|
||||
window.mbInitializingScrollbar = true; // this must precede AS_SetSnapTo
|
||||
// to make persistence of the vertical scrollbar position work
|
||||
|
||||
auto &selectionManager = ProjectSelectionManager::Get( project );
|
||||
selectionManager.AS_SetSnapTo(settings.GetSnapTo());
|
||||
selectionManager.AS_SetSelectionFormat(settings.GetSelectionFormat());
|
||||
selectionManager.SSBL_SetFrequencySelectionFormatName(
|
||||
settings.GetFrequencySelectionFormatName());
|
||||
selectionManager.SSBL_SetBandwidthSelectionFormatName(
|
||||
settings.GetBandwidthSelectionFormatName());
|
||||
|
||||
SelectionBar::Get( project ).SetRate( settings.GetRate() );
|
||||
|
||||
ProjectHistory::Get( project ).InitialState();
|
||||
trackPanel.SetFocusedTrack( *tracks.Any().begin() );
|
||||
window.HandleResize();
|
||||
trackPanel.Refresh(false);
|
||||
trackPanel.Update(); // force any repaint to happen now,
|
||||
// else any asynch calls into the blockfile code will not have
|
||||
// finished logging errors (if any) before the call to ProjectFSCK()
|
||||
|
||||
if (addtohistory)
|
||||
FileHistory::Global().AddFileToHistory(fileName);
|
||||
}
|
||||
|
||||
// Use a finally block here, because there are calls to Save() below which
|
||||
// might throw.
|
||||
bool closed = false;
|
||||
auto cleanup = finally( [&] {
|
||||
//release the flag.
|
||||
ODManager::UnmarkLoadedODFlag();
|
||||
|
||||
if (! closed ) {
|
||||
if ( bParseSuccess ) {
|
||||
// This is a no-fail:
|
||||
dirManager.FillBlockfilesCache();
|
||||
projectFileManager.EnqueueODTasks();
|
||||
}
|
||||
|
||||
// For an unknown reason, OSX requires that the project window be
|
||||
// raised if a recovery took place.
|
||||
window.CallAfter( [&] { window.Raise(); } );
|
||||
}
|
||||
} );
|
||||
|
||||
if (bParseSuccess) {
|
||||
bool saved = false;
|
||||
|
||||
if (projectFileIO.IsRecovered())
|
||||
{
|
||||
// This project has been recovered, so write a NEW auto-save file
|
||||
// now and then DELETE the old one in the auto-save folder. Note that
|
||||
// at this point mFileName != fileName, because when opening a
|
||||
// recovered file mFileName is faked to point to the original file
|
||||
// which has been recovered, not the one in the auto-save folder.
|
||||
::ProjectFSCK(dirManager, err, true); // Correct problems in auto-recover mode.
|
||||
|
||||
// PushState calls AutoSave(), so no longer need to do so here.
|
||||
history.PushState(_("Project was recovered"), _("Recover"));
|
||||
|
||||
if (!wxRemoveFile(fileName))
|
||||
AudacityMessageBox(_("Could not remove old auto save file"),
|
||||
_("Error"), wxICON_STOP, &window);
|
||||
}
|
||||
else
|
||||
{
|
||||
// This is a regular project, check it and ask user
|
||||
int status = ::ProjectFSCK(dirManager, err, false);
|
||||
if (status & FSCKstatus_CLOSE_REQ)
|
||||
{
|
||||
// Vaughan, 2010-08-23: Note this did not do a real close.
|
||||
// It could cause problems if you get this, say on missing alias files,
|
||||
// then try to open a project with, e.g., missing blockfiles.
|
||||
// It then failed in SetProject, saying it cannot find the files,
|
||||
// then never go through ProjectFSCK to give more info.
|
||||
// Going through OnClose() may be overkill, but it's safe.
|
||||
/*
|
||||
// There was an error in the load/check and the user
|
||||
// explictly opted to close the project.
|
||||
mTracks->Clear(true);
|
||||
mFileName = wxT("");
|
||||
SetProjectTitle();
|
||||
mTrackPanel->Refresh(true);
|
||||
*/
|
||||
closed = true;
|
||||
SetMenuClose(true);
|
||||
window.Close();
|
||||
return;
|
||||
}
|
||||
else if (status & FSCKstatus_CHANGED)
|
||||
{
|
||||
// Mark the wave tracks as changed and redraw.
|
||||
for ( auto wt : tracks.Any<WaveTrack>() )
|
||||
// Only wave tracks have a notion of "changed".
|
||||
for (const auto &clip: wt->GetClips())
|
||||
clip->MarkChanged();
|
||||
|
||||
trackPanel.Refresh(true);
|
||||
|
||||
// Vaughan, 2010-08-20: This was bogus, as all the actions in ProjectFSCK
|
||||
// that return FSCKstatus_CHANGED cannot be undone.
|
||||
// this->PushState(_("Project checker repaired file"), _("Project Repair"));
|
||||
|
||||
if (status & FSCKstatus_SAVE_AUP)
|
||||
projectFileManager.Save(), saved = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (mImportXMLTagHandler) {
|
||||
if (!saved)
|
||||
// We processed an <import> tag, so save it as a normal project,
|
||||
// with no <import> tags.
|
||||
projectFileManager.Save();
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Vaughan, 2011-10-30:
|
||||
// See first topic at http://bugzilla.audacityteam.org/show_bug.cgi?id=451#c16.
|
||||
// Calling mTracks->Clear() with deleteTracks true results in data loss.
|
||||
|
||||
// PRL 2014-12-19:
|
||||
// I made many changes for wave track memory management, but only now
|
||||
// read the above comment. I may have invalidated the fix above (which
|
||||
// may have spared the files at the expense of leaked memory). But
|
||||
// here is a better way to accomplish the intent, doing like what happens
|
||||
// when the project closes:
|
||||
for ( auto pTrack : tracks.Any< WaveTrack >() )
|
||||
pTrack->CloseLock();
|
||||
|
||||
tracks.Clear(); //tracks.Clear(true);
|
||||
|
||||
project.SetFileName( wxT("") );
|
||||
projectFileIO.SetProjectTitle();
|
||||
|
||||
wxLogError(wxT("Could not parse file \"%s\". \nError: %s"), fileName, errorStr);
|
||||
|
||||
wxString url = wxT("FAQ:Errors_on_opening_or_recovering_an_Audacity_project");
|
||||
|
||||
// Certain errors have dedicated help.
|
||||
// On April-4th-2018, we did not request translation of the XML errors.
|
||||
// If/when we do, we will need _() around the comparison strings.
|
||||
if( errorStr.Contains( ("not well-formed (invalid token)") ) )
|
||||
url = "Error:_not_well-formed_(invalid_token)_at_line_x";
|
||||
else if( errorStr.Contains(("reference to invalid character number") ))
|
||||
url = "Error_Opening_Project:_Reference_to_invalid_character_number_at_line_x";
|
||||
else if( errorStr.Contains(("mismatched tag") ))
|
||||
url += "#mismatched";
|
||||
|
||||
// These two errors with FAQ entries are reported elsewhere, not here....
|
||||
//#[[#import-error|Error Importing: Aup is an Audacity Project file. Use the File > Open command]]
|
||||
//#[[#corrupt|Error Opening File or Project: File may be invalid or corrupted]]
|
||||
|
||||
// If we did want to handle every single parse error, these are they....
|
||||
/*
|
||||
XML_L("out of memory"),
|
||||
XML_L("syntax error"),
|
||||
XML_L("no element found"),
|
||||
XML_L("not well-formed (invalid token)"),
|
||||
XML_L("unclosed token"),
|
||||
XML_L("partial character"),
|
||||
XML_L("mismatched tag"),
|
||||
XML_L("duplicate attribute"),
|
||||
XML_L("junk after document element"),
|
||||
XML_L("illegal parameter entity reference"),
|
||||
XML_L("undefined entity"),
|
||||
XML_L("recursive entity reference"),
|
||||
XML_L("asynchronous entity"),
|
||||
XML_L("reference to invalid character number"),
|
||||
XML_L("reference to binary entity"),
|
||||
XML_L("reference to external entity in attribute"),
|
||||
XML_L("XML or text declaration not at start of entity"),
|
||||
XML_L("unknown encoding"),
|
||||
XML_L("encoding specified in XML declaration is incorrect"),
|
||||
XML_L("unclosed CDATA section"),
|
||||
XML_L("error in processing external entity reference"),
|
||||
XML_L("document is not standalone"),
|
||||
XML_L("unexpected parser state - please send a bug report"),
|
||||
XML_L("entity declared in parameter entity"),
|
||||
XML_L("requested feature requires XML_DTD support in Expat"),
|
||||
XML_L("cannot change setting once parsing has begun"),
|
||||
XML_L("unbound prefix"),
|
||||
XML_L("must not undeclare prefix"),
|
||||
XML_L("incomplete markup in parameter entity"),
|
||||
XML_L("XML declaration not well-formed"),
|
||||
XML_L("text declaration not well-formed"),
|
||||
XML_L("illegal character(s) in public id"),
|
||||
XML_L("parser suspended"),
|
||||
XML_L("parser not suspended"),
|
||||
XML_L("parsing aborted"),
|
||||
XML_L("parsing finished"),
|
||||
XML_L("cannot suspend in external parameter entity"),
|
||||
XML_L("reserved prefix (xml) must not be undeclared or bound to another namespace name"),
|
||||
XML_L("reserved prefix (xmlns) must not be declared or undeclared"),
|
||||
XML_L("prefix must not be bound to one of the reserved namespace names")
|
||||
*/
|
||||
|
||||
ShowErrorDialog(
|
||||
&window,
|
||||
_("Error Opening Project"),
|
||||
errorStr,
|
||||
url);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector< std::shared_ptr< Track > >
|
||||
ProjectManager::AddImportedTracks(const FilePath &fileName,
|
||||
TrackHolders &&newTracks)
|
||||
{
|
||||
auto &project = mProject;
|
||||
auto &history = ProjectHistory::Get( project );
|
||||
auto &projectFileIO = ProjectFileIO::Get( project );
|
||||
auto &projectFileManager = ProjectFileManager::Get( project );
|
||||
auto &tracks = TrackList::Get( project );
|
||||
|
||||
std::vector< std::shared_ptr< Track > > results;
|
||||
|
||||
SelectActions::SelectNone( project );
|
||||
|
||||
bool initiallyEmpty = tracks.empty();
|
||||
double newRate = 0;
|
||||
wxString trackNameBase = fileName.AfterLast(wxFILE_SEP_PATH).BeforeLast('.');
|
||||
int i = -1;
|
||||
|
||||
// Must add all tracks first (before using Track::IsLeader)
|
||||
for (auto &group : newTracks) {
|
||||
if (group.empty()) {
|
||||
wxASSERT(false);
|
||||
continue;
|
||||
}
|
||||
auto first = group.begin()->get();
|
||||
auto nChannels = group.size();
|
||||
for (auto &uNewTrack : group) {
|
||||
auto newTrack = tracks.Add( uNewTrack );
|
||||
results.push_back(newTrack->SharedPointer());
|
||||
}
|
||||
tracks.GroupChannels(*first, nChannels);
|
||||
}
|
||||
newTracks.clear();
|
||||
|
||||
// Now name them
|
||||
|
||||
// Add numbers to track names only if there is more than one (mono or stereo)
|
||||
// track (not necessarily, more than one channel)
|
||||
const bool useSuffix =
|
||||
make_iterator_range( results.begin() + 1, results.end() )
|
||||
.any_of( []( decltype(*results.begin()) &pTrack )
|
||||
{ return pTrack->IsLeader(); } );
|
||||
|
||||
for (const auto &newTrack : results) {
|
||||
if ( newTrack->IsLeader() )
|
||||
// Count groups only
|
||||
++i;
|
||||
|
||||
newTrack->SetSelected(true);
|
||||
|
||||
if ( useSuffix )
|
||||
newTrack->SetName(trackNameBase + wxString::Format(wxT(" %d" ), i + 1));
|
||||
else
|
||||
newTrack->SetName(trackNameBase);
|
||||
|
||||
newTrack->TypeSwitch( [&](WaveTrack *wt) {
|
||||
if (newRate == 0)
|
||||
newRate = wt->GetRate();
|
||||
|
||||
// Check if NEW track contains aliased blockfiles and if yes,
|
||||
// remember this to show a warning later
|
||||
if(WaveClip* clip = wt->GetClipByIndex(0)) {
|
||||
BlockArray &blocks = clip->GetSequence()->GetBlockArray();
|
||||
if (blocks.size())
|
||||
{
|
||||
SeqBlock& block = blocks[0];
|
||||
if (block.f->IsAlias())
|
||||
projectFileManager.SetImportedDependencies( true );
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Automatically assign rate of imported file to whole project,
|
||||
// if this is the first file that is imported
|
||||
if (initiallyEmpty && newRate > 0) {
|
||||
auto &settings = ProjectSettings::Get( project );
|
||||
settings.SetRate( newRate );
|
||||
SelectionBar::Get( project ).SetRate( newRate );
|
||||
}
|
||||
|
||||
history.PushState(wxString::Format(_("Imported '%s'"), fileName),
|
||||
_("Import"));
|
||||
|
||||
#if defined(__WXGTK__)
|
||||
// See bug #1224
|
||||
// The track panel hasn't we been fully created, so the DoZoomFit() will not give
|
||||
// expected results due to a window width of zero. Should be safe to yield here to
|
||||
// allow the creattion to complete. If this becomes a problem, it "might" be possible
|
||||
// to queue a dummy event to trigger the DoZoomFit().
|
||||
wxEventLoopBase::GetActive()->YieldFor(wxEVT_CATEGORY_UI | wxEVT_CATEGORY_USER_INPUT);
|
||||
#endif
|
||||
|
||||
if (initiallyEmpty && !projectFileIO.IsProjectSaved() ) {
|
||||
wxString name = fileName.AfterLast(wxFILE_SEP_PATH).BeforeLast(wxT('.'));
|
||||
project.SetFileName(
|
||||
::wxPathOnly(fileName) + wxFILE_SEP_PATH + name + wxT(".aup") );
|
||||
projectFileIO.SetLoadedFromAup( false );
|
||||
projectFileIO.SetProjectTitle();
|
||||
}
|
||||
|
||||
// Moved this call to higher levels to prevent flicker redrawing everything on each file.
|
||||
// HandleResize();
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
// If pNewTrackList is passed in non-NULL, it gets filled with the pointers to NEW tracks.
|
||||
bool ProjectManager::Import(
|
||||
const FilePath &fileName, WaveTrackArray* pTrackArray /*= NULL*/)
|
||||
{
|
||||
auto &project = mProject;
|
||||
auto &dirManager = DirManager::Get( project );
|
||||
auto oldTags = Tags::Get( project ).shared_from_this();
|
||||
TrackHolders newTracks;
|
||||
wxString errorMessage;
|
||||
|
||||
{
|
||||
// Backup Tags, before the import. Be prepared to roll back changes.
|
||||
bool committed = false;
|
||||
auto cleanup = finally([&]{
|
||||
if ( !committed )
|
||||
Tags::Set( project, oldTags );
|
||||
});
|
||||
auto newTags = oldTags->Duplicate();
|
||||
Tags::Set( project, newTags );
|
||||
|
||||
bool success = Importer::Get().Import(fileName,
|
||||
&TrackFactory::Get( project ),
|
||||
newTracks,
|
||||
newTags.get(),
|
||||
errorMessage);
|
||||
|
||||
if (!errorMessage.empty()) {
|
||||
// Error message derived from Importer::Import
|
||||
// Additional help via a Help button links to the manual.
|
||||
ShowErrorDialog(&GetProjectFrame( project ), _("Error Importing"),
|
||||
errorMessage, wxT("Importing_Audio"));
|
||||
}
|
||||
if (!success)
|
||||
return false;
|
||||
|
||||
FileHistory::Global().AddFileToHistory(fileName);
|
||||
|
||||
// no more errors, commit
|
||||
committed = true;
|
||||
}
|
||||
|
||||
// for LOF ("list of files") files, do not import the file as if it
|
||||
// were an audio file itself
|
||||
if (fileName.AfterLast('.').IsSameAs(wxT("lof"), false)) {
|
||||
// PRL: don't redundantly do the steps below, because we already
|
||||
// did it in case of LOF, because of some weird recursion back to this
|
||||
// same function. I think this should be untangled.
|
||||
|
||||
// So Undo history push is not bypassed, despite appearances.
|
||||
return false;
|
||||
}
|
||||
|
||||
// PRL: Undo history is incremented inside this:
|
||||
auto newSharedTracks = AddImportedTracks(fileName, std::move(newTracks));
|
||||
|
||||
if (pTrackArray) {
|
||||
for (const auto &newTrack : newSharedTracks) {
|
||||
newTrack->TypeSwitch( [&](WaveTrack *wt) {
|
||||
pTrackArray->push_back( wt->SharedPointer< WaveTrack >() );
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
int mode = gPrefs->Read(wxT("/AudioFiles/NormalizeOnLoad"), 0L);
|
||||
if (mode == 1) {
|
||||
//TODO: All we want is a SelectAll()
|
||||
SelectActions::SelectNone( project );
|
||||
SelectActions::SelectAllIfNone( project );
|
||||
const CommandContext context( project );
|
||||
PluginActions::DoEffect(
|
||||
EffectManager::Get().GetEffectByIdentifier(wxT("Normalize")),
|
||||
context,
|
||||
PluginActions::kConfigured);
|
||||
}
|
||||
|
||||
// This is a no-fail:
|
||||
dirManager.FillBlockfilesCache();
|
||||
return true;
|
||||
}
|
||||
|
||||
// This is done to empty out the tracks, but without creating a new project.
|
||||
void ProjectManager::ResetProjectToEmpty() {
|
||||
auto &project = mProject;
|
||||
|
|
|
@ -12,25 +12,15 @@ Paul Licameli split from AudacityProject.h
|
|||
#define __AUDACITY_PROJECT_MANAGER__
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include <wx/event.h> // to inherit
|
||||
#include "ClientData.h" // to inherit
|
||||
#include "import/ImportRaw.h" // defines TrackHolders
|
||||
|
||||
class wxTimer;
|
||||
class wxTimerEvent;
|
||||
|
||||
class AudacityProject;
|
||||
struct AudioIOStartStreamOptions;
|
||||
class ImportXMLTagHandler;
|
||||
class RecordingRecoveryHandler;
|
||||
class Track;
|
||||
class WaveTrack;
|
||||
class XMLTagHandler;
|
||||
namespace ProjectFileIORegistry{ struct Entry; }
|
||||
|
||||
using WaveTrackArray = std::vector < std::shared_ptr < WaveTrack > >;
|
||||
|
||||
///\brief Object associated with a project for high-level management of the
|
||||
/// project's lifetime, including creation, destruction, opening from file,
|
||||
|
@ -49,45 +39,9 @@ public:
|
|||
// This is the factory for projects:
|
||||
static AudacityProject *New();
|
||||
|
||||
// File I/O
|
||||
|
||||
/** @brief Show an open dialogue for opening audio files, and possibly other
|
||||
* sorts of files.
|
||||
*
|
||||
* The file type filter will automatically contain:
|
||||
* - "All files" with any extension or none,
|
||||
* - "All supported files" based on the file formats supported in this
|
||||
* build of Audacity,
|
||||
* - All of the individual formats specified by the importer plug-ins which
|
||||
* are built into this build of Audacity, each with the relevant file
|
||||
* extensions for that format.
|
||||
* The dialogue will start in the DefaultOpenPath directory read from the
|
||||
* preferences, failing that the working directory. The file format filter
|
||||
* will be set to the DefaultOpenType from the preferences, failing that
|
||||
* the first format specified in the dialogue. These two parameters will
|
||||
* be saved to the preferences once the user has chosen a file to open.
|
||||
* @param extraformat Specify the name of an additional format to allow
|
||||
* opening in this dialogue. This string is free-form, but should be short
|
||||
* enough to fit in the file dialogue filter drop-down. It should be
|
||||
* translated.
|
||||
* @param extrafilter Specify the file extension(s) for the additional format
|
||||
* specified by extraformat. The patterns must include the wildcard (e.g.
|
||||
* "*.aup" not "aup" or ".aup"), separate multiple patters with a semicolon,
|
||||
* e.g. "*.aup;*.AUP" because patterns are case-sensitive. Do not add a
|
||||
* trailing semicolon to the string. This string should not be translated
|
||||
* @return Array of file paths which the user selected to open (multiple
|
||||
* selections allowed).
|
||||
*/
|
||||
static wxArrayString ShowOpenDialog(const wxString &extraformat = {},
|
||||
const wxString &extrafilter = {});
|
||||
|
||||
static bool IsAlreadyOpen(const FilePath &projPathName);
|
||||
|
||||
// The functions that open and import files can act as factories too, and
|
||||
// they set projects to initial state.
|
||||
// The function that imports files can act as a factory too, and for that
|
||||
// reason remains in this class, not in ProjectFileManager
|
||||
static void OpenFiles(AudacityProject *proj);
|
||||
|
||||
void OpenFile(const FilePath &fileName, bool addtohistory = true);
|
||||
|
||||
// Return the given project if that is not NULL, else create a project.
|
||||
// Then open the given project path.
|
||||
|
@ -96,14 +50,6 @@ public:
|
|||
AudacityProject *pProject,
|
||||
const FilePath &fileNameArg, bool addtohistory = true);
|
||||
|
||||
// If pNewTrackList is passed in non-NULL, it gets filled with the pointers to NEW tracks.
|
||||
bool Import(const FilePath &fileName, WaveTrackArray *pTrackArray = NULL);
|
||||
|
||||
// Takes array of unique pointers; returns array of shared
|
||||
std::vector< std::shared_ptr<Track> >
|
||||
AddImportedTracks(const FilePath &fileName,
|
||||
TrackHolders &&newTracks);
|
||||
|
||||
void ResetProjectToEmpty();
|
||||
|
||||
static void SaveWindowSize();
|
||||
|
@ -113,8 +59,6 @@ public:
|
|||
// Converts number of minutes to human readable format
|
||||
wxString GetHoursMinsString(int iMinutes);
|
||||
|
||||
void SetMenuClose(bool value) { mMenuClose = value; }
|
||||
|
||||
private:
|
||||
void OnCloseWindow(wxCloseEvent & event);
|
||||
void OnTimer(wxTimerEvent & event);
|
||||
|
@ -123,28 +67,14 @@ private:
|
|||
|
||||
void RestartTimer();
|
||||
|
||||
// Declared in this class so that they can have access to private members
|
||||
static XMLTagHandler *RecordingRecoveryFactory( AudacityProject &project );
|
||||
static ProjectFileIORegistry::Entry sRecoveryFactory;
|
||||
static XMLTagHandler *ImportHandlerFactory( AudacityProject &project );
|
||||
static ProjectFileIORegistry::Entry sImportHandlerFactory;
|
||||
|
||||
// non-static data members
|
||||
AudacityProject &mProject;
|
||||
|
||||
// The handler that handles recovery of <recordingrecovery> tags
|
||||
std::unique_ptr<RecordingRecoveryHandler> mRecordingRecoveryHandler;
|
||||
|
||||
std::unique_ptr<ImportXMLTagHandler> mImportXMLTagHandler;
|
||||
|
||||
std::unique_ptr<wxTimer> mTimer;
|
||||
|
||||
// See explanation in OnCloseWindow
|
||||
bool mIsBeingDeleted{ false };
|
||||
|
||||
// Are we currently closing as the result of a menu command?
|
||||
bool mMenuClose{ false };
|
||||
|
||||
DECLARE_EVENT_TABLE()
|
||||
|
||||
static bool sbWindowRectAlreadySaved;
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
#include "../Audacity.h"
|
||||
#include "ImportExportCommands.h"
|
||||
|
||||
#include "../ProjectManager.h"
|
||||
#include "../ProjectFileManager.h"
|
||||
#include "../ViewInfo.h"
|
||||
#include "../export/Export.h"
|
||||
#include "../Shuttle.h"
|
||||
|
@ -41,7 +41,7 @@ void ImportCommand::PopulateOrExchange(ShuttleGui & S)
|
|||
}
|
||||
|
||||
bool ImportCommand::Apply(const CommandContext & context){
|
||||
return ProjectManager::Get( context.project ).Import(mFileName);
|
||||
return ProjectFileManager::Get( context.project ).Import(mFileName);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -54,7 +54,7 @@ bool OpenProjectCommand::Apply(const CommandContext & context){
|
|||
}
|
||||
else
|
||||
{
|
||||
ProjectManager::Get( context.project )
|
||||
ProjectFileManager::Get( context.project )
|
||||
.OpenFile(mFileName, mbAddToHistory);
|
||||
}
|
||||
const auto &newFileName = context.project.GetFileName();
|
||||
|
|
|
@ -180,7 +180,7 @@ void OnClose(const CommandContext &context )
|
|||
{
|
||||
auto &project = context.project;
|
||||
auto &window = ProjectWindow::Get( project );
|
||||
ProjectManager::Get( project ).SetMenuClose(true);
|
||||
ProjectFileManager::Get( project ).SetMenuClose(true);
|
||||
window.Close();
|
||||
}
|
||||
|
||||
|
@ -414,7 +414,7 @@ void OnImport(const CommandContext &context)
|
|||
// this serves to track the file if the users zooms in and such.
|
||||
MissingAliasFilesDialog::SetShouldShow(true);
|
||||
|
||||
wxArrayString selectedFiles = ProjectManager::ShowOpenDialog(wxT(""));
|
||||
wxArrayString selectedFiles = ProjectFileManager::ShowOpenDialog(wxT(""));
|
||||
if (selectedFiles.size() == 0) {
|
||||
gPrefs->Write(wxT("/LastOpenType"),wxT(""));
|
||||
gPrefs->Flush();
|
||||
|
@ -444,7 +444,7 @@ void OnImport(const CommandContext &context)
|
|||
|
||||
FileNames::UpdateDefaultPath(FileNames::Operation::Open, fileName);
|
||||
|
||||
ProjectManager::Get( project ).Import(fileName);
|
||||
ProjectFileManager::Get( project ).Import(fileName);
|
||||
}
|
||||
|
||||
window.ZoomAfterImport(nullptr);
|
||||
|
@ -541,7 +541,8 @@ void OnImportRaw(const CommandContext &context)
|
|||
if (newTracks.size() <= 0)
|
||||
return;
|
||||
|
||||
ProjectManager::Get( project ).AddImportedTracks(fileName, std::move(newTracks));
|
||||
ProjectFileManager::Get( project )
|
||||
.AddImportedTracks(fileName, std::move(newTracks));
|
||||
window.HandleResize(); // Adjust scrollers for NEW track sizes.
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue