1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-04-30 15:49:41 +02:00
audacity/src/menus/FileMenus.cpp
Paul Licameli 70ec69fa3d Pass untranslated command labels to CommandManager...
... and deduce whether to exclude from macros inside NewIdentifier, simplifying
argument lists further

Also fix the localization of "..." added to names by PluginMenus.cpp
2019-12-12 15:15:59 -05:00

699 lines
21 KiB
C++

#include "../Audacity.h" // for USE_* macros
#include "../Experimental.h"
#include "../BatchCommands.h"
#include "../CommonCommandFlags.h"
#include "../FileNames.h"
#include "../LabelTrack.h"
#include "../MissingAliasFileDialog.h"
#include "../NoteTrack.h"
#include "../Prefs.h"
#include "../Printing.h"
#include "../Project.h"
#include "../ProjectFileManager.h"
#include "../ProjectHistory.h"
#include "../ProjectManager.h"
#include "../ProjectWindow.h"
#include "../SelectUtilities.h"
#include "../TrackPanel.h"
#include "../ViewInfo.h"
#include "../WaveTrack.h"
#include "../commands/CommandContext.h"
#include "../commands/CommandManager.h"
#include "../export/ExportMultiple.h"
#include "../import/ImportMIDI.h"
#include "../widgets/AudacityMessageBox.h"
#include "../widgets/FileHistory.h"
#ifdef USE_MIDI
#include "../import/ImportMIDI.h"
#endif // USE_MIDI
#include "../ondemand/ODManager.h"
#include <wx/menu.h>
// private helper classes and functions
namespace {
void DoExport
(AudacityProject &project, const wxString & Format )
{
auto &tracks = TrackList::Get( project );
Exporter e;
MissingAliasFilesDialog::SetShouldShow(true);
double t0 = 0.0;
double t1 = tracks.GetEndTime();
// Prompt for file name and/or extension?
bool bPromptingRequired =
(project.mBatchMode == 0) || project.GetFileName().empty() ||
Format.empty();
wxString filename;
if (!bPromptingRequired) {
// We're in batch mode, and we have an mFileName and Format.
wxString extension = ".";
extension += Format;
extension.MakeLower();
filename =
MacroCommands::BuildCleanFileName(project.GetFileName(), extension);
// Bug 1854, No warning of file overwrite
// (when export is called from Macros).
int counter = 0;
bPromptingRequired = wxFileExists(filename);
// We'll try alternative names to avoid overwriting.
while ( bPromptingRequired && counter < 100 ) {
counter++;
wxString number;
number.Printf("%03i", counter);
// So now the name has a number in it too.
filename = MacroCommands::BuildCleanFileName(
project.GetFileName(), number + extension);
bPromptingRequired = wxFileExists(filename);
}
// If we've run out of alternative names, we will fall back to prompting
// - even if in a macro.
}
if (bPromptingRequired)
{
// Do export with prompting.
e.SetDefaultFormat(Format);
e.Process(&project, false, t0, t1);
}
else
{
FileHistory::Global().AddFileToHistory(filename);
// We're in batch mode, the file does not exist already.
// We really can proceed without prompting.
int nChannels = MacroCommands::IsMono() ? 1 : 2;
e.Process(
&project, // AudacityProject
nChannels, // numChannels,
Format, // type,
filename, // filename,
false, // selectedOnly,
t0, // t0
t1 // t1
);
}
}
}
// Menu handler functions
namespace FileActions {
struct Handler : CommandHandlerObject {
void OnNew(const CommandContext & )
{
( void ) ProjectManager::New();
}
void OnOpen(const CommandContext &context )
{
auto &project = context.project;
ProjectManager::OpenFiles(&project);
}
// JKC: This is like OnClose, except it emptys the project in place,
// rather than createing a new empty project (with new toolbars etc).
// It does not test for unsaved changes.
// It is not in the menus by default. Its main purpose is/was for
// developers checking functionality of ResetProjectToEmpty().
void OnProjectReset(const CommandContext &context)
{
auto &project = context.project;
ProjectManager::Get( project ).ResetProjectToEmpty();
}
void OnClose(const CommandContext &context )
{
auto &project = context.project;
auto &window = ProjectWindow::Get( project );
ProjectFileManager::Get( project ).SetMenuClose(true);
window.Close();
}
void OnSave(const CommandContext &context )
{
auto &project = context.project;
auto &projectFileManager = ProjectFileManager::Get( project );
projectFileManager.Save();
}
void OnSaveAs(const CommandContext &context )
{
auto &project = context.project;
auto &projectFileManager = ProjectFileManager::Get( project );
projectFileManager.SaveAs();
}
void OnSaveCopy(const CommandContext &context )
{
auto &project = context.project;
auto &projectFileManager = ProjectFileManager::Get( project );
projectFileManager.SaveAs(true, true);
}
#ifdef USE_LIBVORBIS
void OnSaveCompressed(const CommandContext &context)
{
auto &project = context.project;
auto &projectFileManager = ProjectFileManager::Get( project );
projectFileManager.SaveAs(true);
}
#endif
void OnExportMp3(const CommandContext &context)
{
auto &project = context.project;
DoExport(project, "MP3");
}
void OnExportWav(const CommandContext &context)
{
auto &project = context.project;
DoExport(project, "WAV");
}
void OnExportOgg(const CommandContext &context)
{
auto &project = context.project;
DoExport(project, "OGG");
}
void OnExportAudio(const CommandContext &context)
{
auto &project = context.project;
DoExport(project, "");
}
void OnExportSelection(const CommandContext &context)
{
auto &project = context.project;
auto &selectedRegion = ViewInfo::Get( project ).selectedRegion;
Exporter e;
MissingAliasFilesDialog::SetShouldShow(true);
e.SetFileDialogTitle( _("Export Selected Audio") );
e.Process(&project, true, selectedRegion.t0(),
selectedRegion.t1());
}
void OnExportLabels(const CommandContext &context)
{
auto &project = context.project;
auto &tracks = TrackList::Get( project );
auto &window = GetProjectFrame( project );
/* i18n-hint: filename containing exported text from label tracks */
wxString fName = _("labels.txt");
auto trackRange = tracks.Any<const LabelTrack>();
auto numLabelTracks = trackRange.size();
if (numLabelTracks == 0) {
AudacityMessageBox(_("There are no label tracks to export."));
return;
}
else
fName = (*trackRange.rbegin())->GetName();
fName = FileNames::SelectFile(FileNames::Operation::Export,
_("Export Labels As:"),
wxEmptyString,
fName,
wxT("txt"),
wxT("*.txt"),
wxFD_SAVE | wxFD_OVERWRITE_PROMPT | wxRESIZE_BORDER,
&window);
if (fName.empty())
return;
// Move existing files out of the way. Otherwise wxTextFile will
// append to (rather than replace) the current file.
if (wxFileExists(fName)) {
#ifdef __WXGTK__
wxString safetyFileName = fName + wxT("~");
#else
wxString safetyFileName = fName + wxT(".bak");
#endif
if (wxFileExists(safetyFileName))
wxRemoveFile(safetyFileName);
wxRename(fName, safetyFileName);
}
wxTextFile f(fName);
f.Create();
f.Open();
if (!f.IsOpened()) {
AudacityMessageBox( wxString::Format(
_("Couldn't write to file: %s"), fName ) );
return;
}
for (auto lt : trackRange)
lt->Export(f);
f.Write();
f.Close();
}
void OnExportMultiple(const CommandContext &context)
{
auto &project = context.project;
ExportMultipleDialog em(&project);
MissingAliasFilesDialog::SetShouldShow(true);
em.ShowModal();
}
#ifdef USE_MIDI
void OnExportMIDI(const CommandContext &context)
{
auto &project = context.project;
auto &tracks = TrackList::Get( project );
auto &window = GetProjectFrame( project );
// Make sure that there is
// exactly one NoteTrack selected.
const auto range = tracks.Selected< const NoteTrack >();
const auto numNoteTracksSelected = range.size();
if(numNoteTracksSelected > 1) {
AudacityMessageBox(_(
"Please select only one Note Track at a time."));
return;
}
else if(numNoteTracksSelected < 1) {
AudacityMessageBox(_(
"Please select a Note Track."));
return;
}
wxASSERT(numNoteTracksSelected);
if (!numNoteTracksSelected)
return;
const auto nt = *range.begin();
while(true) {
wxString fName;
fName = FileNames::SelectFile(FileNames::Operation::Export,
_("Export MIDI As:"),
wxEmptyString,
fName,
wxT(".mid|.gro"),
_("MIDI file (*.mid)|*.mid|Allegro file (*.gro)|*.gro"),
wxFD_SAVE | wxFD_OVERWRITE_PROMPT | wxRESIZE_BORDER,
&window);
if (fName.empty())
return;
if(!fName.Contains(wxT("."))) {
fName = fName + wxT(".mid");
}
// Move existing files out of the way. Otherwise wxTextFile will
// append to (rather than replace) the current file.
if (wxFileExists(fName)) {
#ifdef __WXGTK__
wxString safetyFileName = fName + wxT("~");
#else
wxString safetyFileName = fName + wxT(".bak");
#endif
if (wxFileExists(safetyFileName))
wxRemoveFile(safetyFileName);
wxRename(fName, safetyFileName);
}
if(fName.EndsWith(wxT(".mid")) || fName.EndsWith(wxT(".midi"))) {
nt->ExportMIDI(fName);
} else if(fName.EndsWith(wxT(".gro"))) {
nt->ExportAllegro(fName);
} else {
wxString msg = _("You have selected a filename with an unrecognized file extension.\nDo you want to continue?");
wxString title = _("Export MIDI");
int id = AudacityMessageBox(msg, title, wxYES_NO);
if (id == wxNO) {
continue;
} else if (id == wxYES) {
nt->ExportMIDI(fName);
}
}
break;
}
}
#endif // USE_MIDI
void OnImport(const CommandContext &context)
{
auto &project = context.project;
auto &window = ProjectWindow::Get( project );
// An import trigger for the alias missing dialog might not be intuitive, but
// this serves to track the file if the users zooms in and such.
MissingAliasFilesDialog::SetShouldShow(true);
wxArrayString selectedFiles = ProjectFileManager::ShowOpenDialog(wxT(""));
if (selectedFiles.size() == 0) {
gPrefs->Write(wxT("/LastOpenType"),wxT(""));
gPrefs->Flush();
return;
}
// PRL: This affects FFmpegImportPlugin::Open which resets the preference
// to false. Should it also be set to true on other paths that reach
// AudacityProject::Import ?
gPrefs->Write(wxT("/NewImportingSession"), true);
//sort selected files by OD status. Load non 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();
window.HandleResize(); // Adjust scrollers for NEW track sizes.
} );
for (size_t ff = 0; ff < selectedFiles.size(); ff++) {
wxString fileName = selectedFiles[ff];
FileNames::UpdateDefaultPath(FileNames::Operation::Open, fileName);
ProjectFileManager::Get( project ).Import(fileName);
}
window.ZoomAfterImport(nullptr);
}
void OnImportLabels(const CommandContext &context)
{
auto &project = context.project;
auto &trackFactory = TrackFactory::Get( project );
auto &tracks = TrackList::Get( project );
auto &window = ProjectWindow::Get( project );
wxString fileName =
FileNames::SelectFile(FileNames::Operation::Open,
_("Select a text file containing labels"),
wxEmptyString, // Path
wxT(""), // Name
wxT(".txt"), // Extension
_("Text files (*.txt)|*.txt|All files|*"),
wxRESIZE_BORDER, // Flags
&window); // Parent
if (!fileName.empty()) {
wxTextFile f;
f.Open(fileName);
if (!f.IsOpened()) {
AudacityMessageBox(
wxString::Format( _("Could not open file: %s"), fileName ) );
return;
}
auto newTrack = trackFactory.NewLabelTrack();
wxString sTrackName;
wxFileName::SplitPath(fileName, NULL, NULL, &sTrackName, NULL);
newTrack->SetName(sTrackName);
newTrack->Import(f);
SelectUtilities::SelectNone( project );
newTrack->SetSelected(true);
tracks.Add( newTrack );
ProjectHistory::Get( project ).PushState(
wxString::Format(_("Imported labels from '%s'"), fileName),
_("Import Labels"));
window.ZoomAfterImport(nullptr);
}
}
#ifdef USE_MIDI
void OnImportMIDI(const CommandContext &context)
{
auto &project = context.project;
auto &window = GetProjectFrame( project );
wxString fileName = FileNames::SelectFile(FileNames::Operation::Open,
_("Select a MIDI file"),
wxEmptyString, // Path
wxT(""), // Name
wxT(""), // Extension
_("MIDI and Allegro files (*.mid;*.midi;*.gro)|*.mid;*.midi;*.gro|MIDI files (*.mid;*.midi)|*.mid;*.midi|Allegro files (*.gro)|*.gro|All files|*"),
wxRESIZE_BORDER, // Flags
&window); // Parent
if (!fileName.empty())
DoImportMIDI(project, fileName);
}
#endif
void OnImportRaw(const CommandContext &context)
{
auto &project = context.project;
auto &trackFactory = TrackFactory::Get( project );
auto &window = ProjectWindow::Get( project );
wxString fileName =
FileNames::SelectFile(FileNames::Operation::Open,
_("Select any uncompressed audio file"),
wxEmptyString, // Path
wxT(""), // Name
wxT(""), // Extension
_("All files|*"),
wxRESIZE_BORDER, // Flags
&window); // Parent
if (fileName.empty())
return;
TrackHolders newTracks;
::ImportRaw(&window, fileName, &trackFactory, newTracks);
if (newTracks.size() <= 0)
return;
ProjectFileManager::Get( project )
.AddImportedTracks(fileName, std::move(newTracks));
window.HandleResize(); // Adjust scrollers for NEW track sizes.
}
void OnPageSetup(const CommandContext &context)
{
auto &project = context.project;
auto &window = GetProjectFrame( project );
HandlePageSetup(&window);
}
void OnPrint(const CommandContext &context)
{
auto &project = context.project;
auto name = project.GetProjectName();
auto &tracks = TrackList::Get( project );
auto &window = GetProjectFrame( project );
HandlePrint(&window, name, &tracks, TrackPanel::Get( project ));
}
void OnExit(const CommandContext &WXUNUSED(context) )
{
// Simulate the application Exit menu item
wxCommandEvent evt{ wxEVT_MENU, wxID_EXIT };
wxTheApp->AddPendingEvent( evt );
}
}; // struct Handler
} // namespace
static CommandHandlerObject &findCommandHandler(AudacityProject &) {
// Handler is not stateful. Doesn't need a factory registered with
// AudacityProject.
static FileActions::Handler instance;
return instance;
};
// Menu definitions
#define FN(X) findCommandHandler, \
static_cast<CommandFunctorPointer>(& FileActions::Handler :: X)
MenuTable::BaseItemPtr FileMenu( AudacityProject& )
{
using namespace MenuTable;
using Options = CommandManager::Options;
return Menu( _("&File"),
/*i18n-hint: "New" is an action (verb) to create a NEW project*/
Command( wxT("New"), XXO("&New"), FN(OnNew),
AudioIONotBusyFlag, wxT("Ctrl+N") ),
/*i18n-hint: (verb)*/
Command( wxT("Open"), XXO("&Open..."), FN(OnOpen),
AudioIONotBusyFlag, wxT("Ctrl+O") ),
#ifdef EXPERIMENTAL_RESET
// Empty the current project and forget its name and path. DANGEROUS
// It's just for developers.
// Do not translate this menu item (no XXO).
// It MUST not be shown to regular users.
Command( wxT("Reset"), XXO("&Dangerous Reset..."), FN(OnProjectReset),
AudioIONotBusyFlag ),
#endif
/////////////////////////////////////////////////////////////////////////////
Menu(
#ifdef __WXMAC__
/* i18n-hint: This is the name of the menu item on Mac OS X only */
_("Open Recent")
#else
/* i18n-hint: This is the name of the menu item on Windows and Linux */
_("Recent &Files")
#endif
,
Special( [](AudacityProject &, wxMenu &theMenu){
// Recent Files and Recent Projects menus
auto &history = FileHistory::Global();
history.UseMenu( &theMenu );
history.AddFilesToMenu( &theMenu );
wxWeakRef<wxMenu> recentFilesMenu{ &theMenu };
wxTheApp->CallAfter( [=] {
// Bug 143 workaround.
// The bug is in wxWidgets. For a menu that has scrollers,
// the scrollers have an ID of 0 (not wxID_NONE which is -3).
// Therefore wxWidgets attempts to find a help string. See
// wxFrameBase::ShowMenuHelp(int menuId)
// It finds a bogus automatic help string of "Recent &Files"
// from that submenu.
// So we set the help string for command with Id 0 to empty.
if ( recentFilesMenu )
recentFilesMenu->GetParent()->SetHelpString( 0, "" );
} );
} )
),
/////////////////////////////////////////////////////////////////////////////
Command( wxT("Close"), XXO("&Close"), FN(OnClose),
AudioIONotBusyFlag, wxT("Ctrl+W") ),
Separator(),
Menu( _("&Save Project"),
Command( wxT("Save"), XXO("&Save Project"), FN(OnSave),
AudioIONotBusyFlag | UnsavedChangesFlag, wxT("Ctrl+S") ),
Command( wxT("SaveAs"), XXO("Save Project &As..."), FN(OnSaveAs),
AudioIONotBusyFlag ),
// TODO: The next two items should be disabled if project is empty
Command( wxT("SaveCopy"), XXO("Save Lossless Copy of Project..."),
FN(OnSaveCopy), AudioIONotBusyFlag )
#ifdef USE_LIBVORBIS
,
Command( wxT("SaveCompressed"),
XXO("&Save Compressed Copy of Project..."),
FN(OnSaveCompressed), AudioIONotBusyFlag )
#endif
),
Separator(),
Menu( _("&Export"),
// Enable Export audio commands only when there are audio tracks.
Command( wxT("ExportMp3"), XXO("Export as MP&3"), FN(OnExportMp3),
AudioIONotBusyFlag | WaveTracksExistFlag ),
Command( wxT("ExportWav"), XXO("Export as &WAV"), FN(OnExportWav),
AudioIONotBusyFlag | WaveTracksExistFlag ),
Command( wxT("ExportOgg"), XXO("Export as &OGG"), FN(OnExportOgg),
AudioIONotBusyFlag | WaveTracksExistFlag ),
Command( wxT("Export"), XXO("&Export Audio..."), FN(OnExportAudio),
AudioIONotBusyFlag | WaveTracksExistFlag, wxT("Ctrl+Shift+E") ),
// Enable Export Selection commands only when there's a selection.
Command( wxT("ExportSel"), XXO("Expo&rt Selected Audio..."),
FN(OnExportSelection),
AudioIONotBusyFlag | TimeSelectedFlag | WaveTracksSelectedFlag,
Options{}.UseStrictFlags() ),
Command( wxT("ExportLabels"), XXO("Export &Labels..."),
FN(OnExportLabels),
AudioIONotBusyFlag | LabelTracksExistFlag ),
// Enable Export audio commands only when there are audio tracks.
Command( wxT("ExportMultiple"), XXO("Export &Multiple..."),
FN(OnExportMultiple),
AudioIONotBusyFlag | WaveTracksExistFlag, wxT("Ctrl+Shift+L") )
#if defined(USE_MIDI)
,
Command( wxT("ExportMIDI"), XXO("Export MI&DI..."), FN(OnExportMIDI),
AudioIONotBusyFlag | NoteTracksExistFlag )
#endif
),
Menu( _("&Import"),
Command( wxT("ImportAudio"), XXO("&Audio..."), FN(OnImport),
AudioIONotBusyFlag, wxT("Ctrl+Shift+I") ),
Command( wxT("ImportLabels"), XXO("&Labels..."), FN(OnImportLabels),
AudioIONotBusyFlag ),
#ifdef USE_MIDI
Command( wxT("ImportMIDI"), XXO("&MIDI..."), FN(OnImportMIDI),
AudioIONotBusyFlag ),
#endif // USE_MIDI
Command( wxT("ImportRaw"), XXO("&Raw Data..."), FN(OnImportRaw),
AudioIONotBusyFlag )
),
Separator(),
/////////////////////////////////////////////////////////////////////////////
Command( wxT("PageSetup"), XXO("Pa&ge Setup..."), FN(OnPageSetup),
AudioIONotBusyFlag | TracksExistFlag ),
/* i18n-hint: (verb) It's item on a menu. */
Command( wxT("Print"), XXO("&Print..."), FN(OnPrint),
AudioIONotBusyFlag | TracksExistFlag ),
Separator(),
// On the Mac, the Exit item doesn't actually go here...wxMac will
// pull it out
// and put it in the Audacity menu for us based on its ID.
/* i18n-hint: (verb) It's item on a menu. */
Command( wxT("Exit"), XXO("E&xit"), FN(OnExit),
AlwaysEnabledFlag, wxT("Ctrl+Q") )
);
}
#undef FN