audacia/src/ProjectManager.cpp

883 lines
30 KiB
C++

/**********************************************************************
Audacity: A Digital Audio Editor
ProjectManager.cpp
Paul Licameli split from AudacityProject.cpp
**********************************************************************/
#include "ProjectManager.h"
#include "Experimental.h"
#include "AdornedRulerPanel.h"
#include "AudioIO.h"
#include "AutoRecovery.h"
#include "BlockFile.h"
#include "Clipboard.h"
#include "DirManager.h"
#include "FileNames.h"
#include "Menus.h"
#include "MissingAliasFileDialog.h"
#include "ModuleManager.h"
#include "Project.h"
#include "ProjectAudioIO.h"
#include "ProjectAudioManager.h"
#include "ProjectFileIO.h"
#include "ProjectFileManager.h"
#include "ProjectHistory.h"
#include "ProjectSelectionManager.h"
#include "ProjectSettings.h"
#include "ProjectWindow.h"
#include "TrackPanel.h"
#include "UndoManager.h"
#include "WaveTrack.h"
#include "wxFileNameWrapper.h"
#include "ondemand/ODManager.h"
#include "prefs/QualityPrefs.h"
#include "toolbars/ControlToolBar.h"
#include "toolbars/MixerToolBar.h"
#include "toolbars/SelectionBar.h"
#include "toolbars/SpectralSelectionBar.h"
#include "toolbars/ToolManager.h"
#include "widgets/AudacityMessageBox.h"
#include "widgets/FileHistory.h"
#include "widgets/ErrorDialog.h"
#include <wx/dataobj.h>
#include <wx/dnd.h>
const int AudacityProjectTimerID = 5200;
static AudacityProject::AttachedObjects::RegisteredFactory sProjectManagerKey {
[]( AudacityProject &project ) {
return std::make_shared< ProjectManager >( project );
}
};
ProjectManager &ProjectManager::Get( AudacityProject &project )
{
return project.AttachedObjects::Get< ProjectManager >( sProjectManagerKey );
}
const ProjectManager &ProjectManager::Get( const AudacityProject &project )
{
return Get( const_cast< AudacityProject & >( project ) );
}
ProjectManager::ProjectManager( AudacityProject &project )
: mProject{ project }
, mTimer{ std::make_unique<wxTimer>(this, AudacityProjectTimerID) }
{
auto &window = ProjectWindow::Get( mProject );
window.Bind( wxEVT_CLOSE_WINDOW, &ProjectManager::OnCloseWindow, this );
mProject.Bind(EVT_PROJECT_STATUS_UPDATE,
&ProjectManager::OnStatusChange, this);
}
ProjectManager::~ProjectManager() = default;
// PRL: This event type definition used to be in AudacityApp.h, which created
// a bad compilation dependency. The event was never emitted anywhere. I
// preserve it and its handler here but I move it to remove the dependency.
// Asynchronous open
wxDECLARE_EXPORTED_EVENT(AUDACITY_DLL_API,
EVT_OPEN_AUDIO_FILE, wxCommandEvent);
wxDEFINE_EVENT(EVT_OPEN_AUDIO_FILE, wxCommandEvent);
BEGIN_EVENT_TABLE( ProjectManager, wxEvtHandler )
EVT_COMMAND(wxID_ANY, EVT_OPEN_AUDIO_FILE, ProjectManager::OnOpenAudioFile)
EVT_TIMER(AudacityProjectTimerID, ProjectManager::OnTimer)
END_EVENT_TABLE()
bool ProjectManager::sbWindowRectAlreadySaved = false;
void ProjectManager::SaveWindowSize()
{
if (sbWindowRectAlreadySaved)
{
return;
}
bool validWindowForSaveWindowSize = FALSE;
ProjectWindow * validProject = nullptr;
bool foundIconizedProject = FALSE;
for ( auto pProject : AllProjects{} )
{
auto &window = ProjectWindow::Get( *pProject );
if (!window.IsIconized()) {
validWindowForSaveWindowSize = TRUE;
validProject = &window;
break;
}
else
foundIconizedProject = TRUE;
}
if (validWindowForSaveWindowSize)
{
wxRect windowRect = validProject->GetRect();
wxRect normalRect = validProject->GetNormalizedWindowState();
bool wndMaximized = validProject->IsMaximized();
gPrefs->Write(wxT("/Window/X"), windowRect.GetX());
gPrefs->Write(wxT("/Window/Y"), windowRect.GetY());
gPrefs->Write(wxT("/Window/Width"), windowRect.GetWidth());
gPrefs->Write(wxT("/Window/Height"), windowRect.GetHeight());
gPrefs->Write(wxT("/Window/Maximized"), wndMaximized);
gPrefs->Write(wxT("/Window/Normal_X"), normalRect.GetX());
gPrefs->Write(wxT("/Window/Normal_Y"), normalRect.GetY());
gPrefs->Write(wxT("/Window/Normal_Width"), normalRect.GetWidth());
gPrefs->Write(wxT("/Window/Normal_Height"), normalRect.GetHeight());
gPrefs->Write(wxT("/Window/Iconized"), FALSE);
}
else
{
if (foundIconizedProject) {
validProject = &ProjectWindow::Get( **AllProjects{}.begin() );
bool wndMaximized = validProject->IsMaximized();
wxRect normalRect = validProject->GetNormalizedWindowState();
// store only the normal rectangle because the itemized rectangle
// makes no sense for an opening project window
gPrefs->Write(wxT("/Window/X"), normalRect.GetX());
gPrefs->Write(wxT("/Window/Y"), normalRect.GetY());
gPrefs->Write(wxT("/Window/Width"), normalRect.GetWidth());
gPrefs->Write(wxT("/Window/Height"), normalRect.GetHeight());
gPrefs->Write(wxT("/Window/Maximized"), wndMaximized);
gPrefs->Write(wxT("/Window/Normal_X"), normalRect.GetX());
gPrefs->Write(wxT("/Window/Normal_Y"), normalRect.GetY());
gPrefs->Write(wxT("/Window/Normal_Width"), normalRect.GetWidth());
gPrefs->Write(wxT("/Window/Normal_Height"), normalRect.GetHeight());
gPrefs->Write(wxT("/Window/Iconized"), TRUE);
}
else {
// this would be a very strange case that might possibly occur on the Mac
// Audacity would have to be running with no projects open
// in this case we are going to write only the default values
wxRect defWndRect;
GetDefaultWindowRect(&defWndRect);
gPrefs->Write(wxT("/Window/X"), defWndRect.GetX());
gPrefs->Write(wxT("/Window/Y"), defWndRect.GetY());
gPrefs->Write(wxT("/Window/Width"), defWndRect.GetWidth());
gPrefs->Write(wxT("/Window/Height"), defWndRect.GetHeight());
gPrefs->Write(wxT("/Window/Maximized"), FALSE);
gPrefs->Write(wxT("/Window/Normal_X"), defWndRect.GetX());
gPrefs->Write(wxT("/Window/Normal_Y"), defWndRect.GetY());
gPrefs->Write(wxT("/Window/Normal_Width"), defWndRect.GetWidth());
gPrefs->Write(wxT("/Window/Normal_Height"), defWndRect.GetHeight());
gPrefs->Write(wxT("/Window/Iconized"), FALSE);
}
}
gPrefs->Flush();
sbWindowRectAlreadySaved = true;
}
#if wxUSE_DRAG_AND_DROP
class FileObject final : public wxFileDataObject
{
public:
FileObject()
{
}
bool IsSupportedFormat(const wxDataFormat & format, Direction WXUNUSED(dir = Get)) const
// PRL: This function does NOT override any inherited virtual! What does it do?
{
if (format.GetType() == wxDF_FILENAME) {
return true;
}
#if defined(__WXMAC__)
#if !wxCHECK_VERSION(3, 0, 0)
if (format.GetFormatId() == kDragPromisedFlavorFindFile) {
return true;
}
#endif
#endif
return false;
}
};
class DropTarget final : public wxFileDropTarget
{
public:
DropTarget(AudacityProject *proj)
{
mProject = proj;
// SetDataObject takes ownership
SetDataObject(safenew FileObject());
}
~DropTarget()
{
}
#if defined(__WXMAC__)
#if !wxCHECK_VERSION(3, 0, 0)
bool GetData() override
{
bool foundSupported = false;
bool firstFileAdded = false;
OSErr result;
UInt16 items = 0;
CountDragItems((DragReference)m_currentDrag, &items);
for (UInt16 index = 1; index <= items; index++) {
DragItemRef theItem = 0;
GetDragItemReferenceNumber((DragReference)m_currentDrag, index, &theItem);
UInt16 flavors = 0;
CountDragItemFlavors((DragReference)m_currentDrag, theItem , &flavors ) ;
for (UInt16 flavor = 1 ;flavor <= flavors; flavor++) {
FlavorType theType = 0;
result = GetFlavorType((DragReference)m_currentDrag, theItem, flavor, &theType);
if (theType != kDragPromisedFlavorFindFile && theType != kDragFlavorTypeHFS) {
continue;
}
foundSupported = true;
Size dataSize = 0;
GetFlavorDataSize((DragReference)m_currentDrag, theItem, theType, &dataSize);
ArrayOf<char> theData{ dataSize };
GetFlavorData((DragReference)m_currentDrag, theItem, theType, (void*) theData.get(), &dataSize, 0L);
wxString name;
if (theType == kDragPromisedFlavorFindFile) {
name = wxMacFSSpec2MacFilename((FSSpec *)theData.get());
}
else if (theType == kDragFlavorTypeHFS) {
name = wxMacFSSpec2MacFilename(&((HFSFlavor *)theData.get())->fileSpec);
}
if (!firstFileAdded) {
// reset file list
((wxFileDataObject*)GetDataObject())->SetData(0, "");
firstFileAdded = true;
}
((wxFileDataObject*)GetDataObject())->AddFile(name);
// We only want to process one flavor
break;
}
}
return foundSupported;
}
#endif
bool OnDrop(wxCoord x, wxCoord y) override
{
// bool foundSupported = false;
#if !wxCHECK_VERSION(3, 0, 0)
bool firstFileAdded = false;
OSErr result;
UInt16 items = 0;
CountDragItems((DragReference)m_currentDrag, &items);
for (UInt16 index = 1; index <= items; index++) {
DragItemRef theItem = 0;
GetDragItemReferenceNumber((DragReference)m_currentDrag, index, &theItem);
UInt16 flavors = 0;
CountDragItemFlavors((DragReference)m_currentDrag, theItem , &flavors ) ;
for (UInt16 flavor = 1 ;flavor <= flavors; flavor++) {
FlavorType theType = 0;
result = GetFlavorType((DragReference)m_currentDrag, theItem, flavor, &theType);
if (theType != kDragPromisedFlavorFindFile && theType != kDragFlavorTypeHFS) {
continue;
}
return true;
}
}
#endif
return CurrentDragHasSupportedFormat();
}
#endif
bool OnDropFiles(wxCoord WXUNUSED(x), wxCoord WXUNUSED(y), const wxArrayString& filenames) override
{
// Experiment shows that this function can be reached while there is no
// catch block above in wxWidgets. So stop all exceptions here.
return GuardedCall< bool > ( [&] {
//sort by OD non OD. load Non OD first so user can start editing asap.
wxArrayString sortednames(filenames);
sortednames.Sort(CompareNoCaseFileName);
ODManager::Pauser pauser;
auto cleanup = finally( [&] {
ProjectWindow::Get( *mProject ).HandleResize(); // Adjust scrollers for NEW track sizes.
} );
for (const auto &name : sortednames) {
#ifdef USE_MIDI
if (FileNames::IsMidi(name))
FileActions::DoImportMIDI(mProject, name);
else
#endif
ProjectFileManager::Get( *mProject ).Import(name);
}
auto &window = ProjectWindow::Get( *mProject );
window.ZoomAfterImport(nullptr);
return true;
} );
}
private:
AudacityProject *mProject;
};
#endif
AudacityProject *ProjectManager::New()
{
wxRect wndRect;
bool bMaximized = false;
bool bIconized = false;
GetNextWindowPlacement(&wndRect, &bMaximized, &bIconized);
// Create and show a NEW project
// Use a non-default deleter in the smart pointer!
auto sp = std::make_shared< AudacityProject >();
AllProjects{}.Add( sp );
auto p = sp.get();
auto &project = *p;
auto &projectHistory = ProjectHistory::Get( project );
auto &projectManager = Get( project );
auto &window = ProjectWindow::Get( *p );
window.Init();
ProjectFileIO::Get( *p ).SetProjectTitle();
MissingAliasFilesDialog::SetShouldShow(true);
MenuManager::Get( project ).CreateMenusAndCommands( project );
projectHistory.InitialState();
projectManager.RestartTimer();
// wxGTK3 seems to need to require creating the window using default position
// and then manually positioning it.
window.SetPosition(wndRect.GetPosition());
if(bMaximized) {
window.Maximize(true);
}
else if (bIconized) {
// if the user close down and iconized state we could start back up and iconized state
// window.Iconize(TRUE);
}
//Initialise the Listeners
gAudioIO->SetListener( &ProjectAudioManager::Get( project ) );
auto &projectSelectionManager = ProjectSelectionManager::Get( project );
SelectionBar::Get( project ).SetListener( &projectSelectionManager );
#ifdef EXPERIMENTAL_SPECTRAL_EDITING
SpectralSelectionBar::Get( project ).SetListener( &projectSelectionManager );
#endif
#if wxUSE_DRAG_AND_DROP
// We can import now, so become a drag target
// SetDropTarget(safenew AudacityDropTarget(this));
// mTrackPanel->SetDropTarget(safenew AudacityDropTarget(this));
// SetDropTarget takes ownership
TrackPanel::Get( project ).SetDropTarget( safenew DropTarget( &project ) );
#endif
//Set the NEW project as active:
SetActiveProject(p);
// Okay, GetActiveProject() is ready. Now we can get its CommandManager,
// and add the shortcut keys to the tooltips.
ToolManager::Get( *p ).RegenerateTooltips();
ModuleManager::Get().Dispatch(ProjectInitialized);
window.Show(true);
return p;
}
// LL: All objects that have a reference to the DirManager should
// be deleted before the final mDirManager->Deref() in this
// routine. Failing to do so can cause unwanted recursion
// and/or attempts to DELETE objects twice.
void ProjectManager::OnCloseWindow(wxCloseEvent & event)
{
auto &project = mProject;
auto &projectFileIO = ProjectFileIO::Get( project );
auto &projectFileManager = ProjectFileManager::Get( project );
const auto &settings = ProjectSettings::Get( project );
auto &projectAudioIO = ProjectAudioIO::Get( project );
auto &tracks = TrackList::Get( project );
auto &window = ProjectWindow::Get( project );
// We are called for the wxEVT_CLOSE_WINDOW, wxEVT_END_SESSION, and
// wxEVT_QUERY_END_SESSION, so we have to protect against multiple
// entries. This is a hack until the whole application termination
// process can be reviewed and reworked. (See bug #964 for ways
// to exercise the bug that instigated this hack.)
if (window.IsBeingDeleted())
{
event.Skip();
return;
}
if (event.CanVeto() && (::wxIsBusy() || project.mbBusyImporting))
{
event.Veto();
return;
}
// Check to see if we were playing or recording
// audio, and if so, make sure Audio I/O is completely finished.
// The main point of this is to properly push the state
// and flush the tracks once we've completely finished
// recording NEW state.
// This code is derived from similar code in
// AudacityProject::~AudacityProject() and TrackPanel::OnTimer().
if (projectAudioIO.GetAudioIOToken()>0 &&
gAudioIO->IsStreamActive(projectAudioIO.GetAudioIOToken())) {
// We were playing or recording audio, but we've stopped the stream.
wxCommandEvent dummyEvent;
ControlToolBar::Get( project ).OnStop(dummyEvent);
window.FixScrollbars();
projectAudioIO.SetAudioIOToken(0);
window.RedrawProject();
}
else if (gAudioIO->IsMonitoring()) {
gAudioIO->StopStream();
}
// MY: Use routine here so other processes can make same check
bool bHasTracks = !tracks.empty();
// We may not bother to prompt the user to save, if the
// project is now empty.
if (event.CanVeto() && (settings.EmptyCanBeDirty() || bHasTracks)) {
if ( UndoManager::Get( project ).UnsavedChanges() ) {
TitleRestorer Restorer( window, project );// RAII
/* i18n-hint: The first %s numbers the project, the second %s is the project name.*/
wxString Title = wxString::Format(_("%sSave changes to %s?"), Restorer.sProjNumber, Restorer.sProjName);
wxString Message = _("Save project before closing?");
if( !bHasTracks )
{
Message += _("\nIf saved, the project will have no tracks.\n\nTo save any previously open tracks:\nCancel, Edit > Undo until all tracks\nare open, then File > Save Project.");
}
int result = AudacityMessageBox( Message,
Title,
wxYES_NO | wxCANCEL | wxICON_QUESTION,
&window);
if (result == wxCANCEL || (result == wxYES &&
!GuardedCall<bool>( [&]{ return projectFileManager.Save(); } )
)) {
event.Veto();
return;
}
}
}
#ifdef __WXMAC__
// Fix bug apparently introduced into 2.1.2 because of wxWidgets 3:
// closing a project that was made full-screen (as by clicking the green dot
// or command+/; not merely "maximized" as by clicking the title bar or
// Zoom in the Window menu) leaves the screen black.
// Fix it by un-full-screening.
// (But is there a different way to do this? What do other applications do?
// I don't see full screen windows of Safari shrinking, but I do see
// momentary blackness.)
window.ShowFullScreen(false);
#endif
ModuleManager::Get().Dispatch(ProjectClosing);
// Stop the timer since there's no need to update anything anymore
mTimer.reset();
// The project is now either saved or the user doesn't want to save it,
// so there's no need to keep auto save info around anymore
projectFileIO.DeleteCurrentAutoSaveFile();
// DMM: Save the size of the last window the user closes
//
// LL: Save before doing anything else to the window that might make
// its size change.
SaveWindowSize();
window.SetIsBeingDeleted();
// Mac: we never quit as the result of a close.
// Other systems: we quit only when the close is the result of an external
// command (on Windows, those are taskbar closes, "X" box, Alt+F4, etc.)
bool quitOnClose;
#ifdef __WXMAC__
quitOnClose = false;
#else
quitOnClose = !projectFileManager.GetMenuClose();
#endif
// DanH: If we're definitely about to quit, clear the clipboard.
// Doing this after Deref'ing the DirManager causes problems.
if ((AllProjects{}.size() == 1) &&
(quitOnClose || AllProjects::Closing()))
Clipboard::Get().Clear();
// JKC: For Win98 and Linux do not detach the menu bar.
// We want wxWidgets to clean it up for us.
// TODO: Is there a Mac issue here??
// SetMenuBar(NULL);
projectFileManager.CloseLock();
// Some of the AdornedRulerPanel functions refer to the TrackPanel, so destroy this
// before the TrackPanel is destroyed. This change was needed to stop Audacity
// crashing when running with Jaws on Windows 10 1703.
AdornedRulerPanel::Destroy( project );
// Destroy the TrackPanel early so it's not around once we start
// deleting things like tracks and such out from underneath it.
// Check validity of mTrackPanel per bug 584 Comment 1.
// Deeper fix is in the Import code, but this failsafes against crash.
TrackPanel::Destroy( project );
// Finalize the tool manager before the children since it needs
// to save the state of the toolbars.
ToolManager::Get( project ).Destroy();
window.DestroyChildren();
TrackFactory::Destroy( project );
// Delete all the tracks to free up memory and DirManager references.
tracks.Clear();
// This must be done before the following Deref() since it holds
// references to the DirManager.
UndoManager::Get( project ).ClearStates();
// MM: Tell the DirManager it can now DELETE itself
// if it finds it is no longer needed. If it is still
// used (f.e. by the clipboard), it will recognize this
// and will destroy itself later.
//
// LL: All objects with references to the DirManager should
// have been deleted before this.
DirManager::Destroy( project );
// Remove self from the global array, but defer destruction of self
auto pSelf = AllProjects{}.Remove( project );
wxASSERT( pSelf );
if (GetActiveProject() == &project) {
// Find a NEW active project
if ( !AllProjects{}.empty() ) {
SetActiveProject(AllProjects{}.begin()->get());
}
else {
SetActiveProject(NULL);
}
}
// Since we're going to be destroyed, make sure we're not to
// receive audio notifications anymore.
if ( gAudioIO->GetListener() == &ProjectAudioManager::Get( project ) ) {
auto active = GetActiveProject();
gAudioIO->SetListener(
active ? &ProjectAudioManager::Get( *active ) : nullptr
);
}
if (AllProjects{}.empty() && !AllProjects::Closing()) {
#if !defined(__WXMAC__)
if (quitOnClose) {
// Simulate the application Exit menu item
wxCommandEvent evt{ wxEVT_MENU, wxID_EXIT };
wxTheApp->AddPendingEvent( evt );
}
else {
sbWindowRectAlreadySaved = false;
// For non-Mac, always keep at least one project window open
(void) New();
}
#endif
}
window.Destroy();
// Destroys this
pSelf.reset();
}
// PRL: I preserve this handler function for an event that was never sent, but
// I don't know the intention.
void ProjectManager::OnOpenAudioFile(wxCommandEvent & event)
{
auto &project = mProject;
auto &window = GetProjectFrame( project );
const wxString &cmd = event.GetString();
if (!cmd.empty())
ProjectFileManager::Get( mProject ).OpenFile(cmd);
window.RequestUserAttention();
}
// static method, can be called outside of a project
void ProjectManager::OpenFiles(AudacityProject *proj)
{
/* i18n-hint: This string is a label in the file type filter in the open
* and save dialogues, for the option that only shows project files created
* with Audacity. Do not include pipe symbols or .aup (this extension will
* now be added automatically for the Save Projects dialogues).*/
auto selectedFiles =
ProjectFileManager::ShowOpenDialog(_("Audacity projects"), wxT("*.aup"));
if (selectedFiles.size() == 0) {
gPrefs->Write(wxT("/LastOpenType"),wxT(""));
gPrefs->Flush();
return;
}
//sort selected files by OD status.
//For the open menu we load OD first so user can edit asap.
//first sort selectedFiles.
selectedFiles.Sort(CompareNoCaseFileName);
ODManager::Pauser pauser;
auto cleanup = finally( [] {
gPrefs->Write(wxT("/LastOpenType"),wxT(""));
gPrefs->Flush();
} );
for (size_t ff = 0; ff < selectedFiles.size(); ff++) {
const wxString &fileName = selectedFiles[ff];
// Make sure it isn't already open.
if (ProjectFileManager::IsAlreadyOpen(fileName))
continue; // Skip ones that are already open.
FileNames::UpdateDefaultPath(FileNames::Operation::Open, fileName);
// DMM: If the project is dirty, that means it's been touched at
// all, and it's not safe to open a NEW project directly in its
// place. Only if the project is brand-NEW clean and the user
// hasn't done any action at all is it safe for Open to take place
// inside the current project.
//
// If you try to Open a NEW project inside the current window when
// there are no tracks, but there's an Undo history, etc, then
// bad things can happen, including data files moving to the NEW
// project directory, etc.
if ( proj && (
ProjectHistory::Get( *proj ).GetDirty() ||
!TrackList::Get( *proj ).empty()
) )
proj = nullptr;
// This project is clean; it's never been touched. Therefore
// all relevant member variables are in their initial state,
// and it's okay to open a NEW project inside this window.
proj = OpenProject( proj, fileName );
}
}
AudacityProject *ProjectManager::OpenProject(
AudacityProject *pProject, const FilePath &fileNameArg, bool addtohistory)
{
AudacityProject *pNewProject = nullptr;
if ( ! pProject )
pProject = pNewProject = New();
auto cleanup = finally( [&] {
if( pNewProject )
GetProjectFrame( *pNewProject ).Close(true);
} );
ProjectFileManager::Get( *pProject ).OpenFile( fileNameArg, addtohistory );
pNewProject = nullptr;
auto &projectFileIO = ProjectFileIO::Get( *pProject );
if( projectFileIO.IsRecovered() )
ProjectWindow::Get( *pProject ).Zoom(
ViewActions::GetZoomOfToFit( *pProject ) );
return pProject;
}
// This is done to empty out the tracks, but without creating a new project.
void ProjectManager::ResetProjectToEmpty() {
auto &project = mProject;
auto &projectFileIO = ProjectFileIO::Get( project );
auto &projectFileManager = ProjectFileManager::Get( project );
auto &projectHistory = ProjectHistory::Get( project );
auto &viewInfo = ViewInfo::Get( project );
SelectActions::DoSelectAll( project );
TrackActions::DoRemoveTracks( project );
// A new DirManager.
DirManager::Reset( project );
TrackFactory::Reset( project );
projectFileManager.Reset();
projectHistory.SetDirty( false );
auto &undoManager = UndoManager::Get( project );
undoManager.ClearStates();
}
void ProjectManager::RestartTimer()
{
if (mTimer) {
// mTimer->Stop(); // not really needed
mTimer->Start( 3000 ); // Update messages as needed once every 3 s.
}
}
void ProjectManager::OnTimer(wxTimerEvent& WXUNUSED(event))
{
auto &project = mProject;
auto &projectAudioIO = ProjectAudioIO::Get( project );
auto &window = GetProjectFrame( project );
auto &dirManager = DirManager::Get( project );
auto mixerToolBar = &MixerToolBar::Get( project );
mixerToolBar->UpdateControls();
auto &statusBar = *window.GetStatusBar();
// gAudioIO->GetNumCaptureChannels() should only be positive
// when we are recording.
if (projectAudioIO.GetAudioIOToken() > 0 && gAudioIO->GetNumCaptureChannels() > 0) {
wxLongLong freeSpace = dirManager.GetFreeDiskSpace();
if (freeSpace >= 0) {
wxString sMessage;
int iRecordingMins = GetEstimatedRecordingMinsLeftOnDisk(gAudioIO->GetNumCaptureChannels());
sMessage.Printf(_("Disk space remaining for recording: %s"), GetHoursMinsString(iRecordingMins));
// Do not change mLastMainStatusMessage
statusBar.SetStatusText(sMessage, mainStatusBarField);
}
}
else if(ODManager::IsInstanceCreated())
{
//if we have some tasks running, we should say something about it.
int numTasks = ODManager::Instance()->GetTotalNumTasks();
if(numTasks)
{
wxString msg;
float ratioComplete= ODManager::Instance()->GetOverallPercentComplete();
if(ratioComplete>=1.0f)
{
//if we are 100 percent complete and there is still a task in the queue, we should wake the ODManager
//so it can clear it.
//signal the od task queue loop to wake up so it can remove the tasks from the queue and the queue if it is empty.
ODManager::Instance()->SignalTaskQueueLoop();
msg = _("On-demand import and waveform calculation complete.");
statusBar.SetStatusText(msg, mainStatusBarField);
}
else if(numTasks>1)
msg.Printf(_("Import(s) complete. Running %d on-demand waveform calculations. Overall %2.0f%% complete."),
numTasks,ratioComplete*100.0);
else
msg.Printf(_("Import complete. Running an on-demand waveform calculation. %2.0f%% complete."),
ratioComplete*100.0);
statusBar.SetStatusText(msg, mainStatusBarField);
}
}
// As also with the TrackPanel timer: wxTimer may be unreliable without
// some restarts
RestartTimer();
}
void ProjectManager::OnStatusChange( wxCommandEvent & )
{
auto &project = mProject;
auto &window = GetProjectFrame( project );
const auto &msg = project.GetStatus();
window.GetStatusBar()->SetStatusText(msg, mainStatusBarField);
// When recording, let the NEW status message stay at least as long as
// the timer interval (if it is not replaced again by this function),
// before replacing it with the message about remaining disk capacity.
RestartTimer();
}
wxString ProjectManager::GetHoursMinsString(int iMinutes)
{
wxString sFormatted;
if (iMinutes < 1) {
// Less than a minute...
sFormatted = _("Less than 1 minute");
return sFormatted;
}
// Calculate
int iHours = iMinutes / 60;
int iMins = iMinutes % 60;
auto sHours =
wxString::Format( wxPLURAL("%d hour", "%d hours", iHours), iHours );
auto sMins =
wxString::Format( wxPLURAL("%d minute", "%d minutes", iMins), iMins );
/* i18n-hint: A time in hours and minutes. Only translate the "and". */
sFormatted.Printf( _("%s and %s."), sHours, sMins);
return sFormatted;
}
// This routine will give an estimate of how many
// minutes of recording time we have available.
// The calculations made are based on the user's current
// preferences.
int ProjectManager::GetEstimatedRecordingMinsLeftOnDisk(long lCaptureChannels) {
auto &project = mProject;
// Obtain the current settings
auto oCaptureFormat = QualityPrefs::SampleFormatChoice();
if (lCaptureChannels == 0) {
gPrefs->Read(wxT("/AudioIO/RecordChannels"), &lCaptureChannels, 2L);
}
// Find out how much free space we have on disk
wxLongLong lFreeSpace = DirManager::Get( project ).GetFreeDiskSpace();
if (lFreeSpace < 0) {
return 0;
}
// Calculate the remaining time
double dRecTime = 0.0;
double bytesOnDiskPerSample = SAMPLE_SIZE_DISK(oCaptureFormat);
dRecTime = lFreeSpace.GetHi() * 4294967296.0 + lFreeSpace.GetLo();
dRecTime /= bytesOnDiskPerSample;
dRecTime /= lCaptureChannels;
dRecTime /= ProjectSettings::Get( project ).GetRate();
// Convert to minutes before returning
int iRecMins = (int)round(dRecTime / 60.0);
return iRecMins;
}