/********************************************************************** Audacity: A Digital Audio Editor ApplyMacroDialog.cpp Dominic Mazzoni James Crook *******************************************************************//*! \class ApplyMacroDialog \brief Shows progress in executing commands in MacroCommands. *//*******************************************************************/ #include "BatchProcessDialog.h" #include // for wxUSE_* macros #ifdef __WXMSW__ #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include "Clipboard.h" #include "ShuttleGui.h" #include "Menus.h" #include "Prefs.h" #include "Project.h" #include "ProjectFileManager.h" #include "ProjectHistory.h" #include "ProjectManager.h" #include "ProjectWindow.h" #include "SelectUtilities.h" #include "commands/CommandManager.h" #include "effects/Effect.h" #include "../images/Arrow.xpm" #include "../images/Empty9x16.xpm" #include "UndoManager.h" #include "AllThemeResources.h" #include "widgets/FileDialog/FileDialog.h" #include "FileNames.h" #include "import/Import.h" #include "widgets/ErrorDialog.h" #include "widgets/AudacityMessageBox.h" #include "widgets/HelpSystem.h" #if wxUSE_ACCESSIBILITY #include "widgets/WindowAccessible.h" #endif #define MacrosPaletteTitle XO("Macros Palette") #define ManageMacrosTitle XO("Manage Macros") // Separate numerical range from the additional buttons // in the expanded view (which start at 10,000). #define MacrosListID 7001 #define CommandsListID 7002 #define ApplyToProjectID 7003 #define ApplyToFilesID 7004 #define ExpandID 7005 #define ShrinkID 7006 BEGIN_EVENT_TABLE(ApplyMacroDialog, wxDialogWrapper) EVT_BUTTON(ApplyToProjectID, ApplyMacroDialog::OnApplyToProject) EVT_BUTTON(ApplyToFilesID, ApplyMacroDialog::OnApplyToFiles) EVT_BUTTON(wxID_CANCEL, ApplyMacroDialog::OnCancel) EVT_BUTTON(wxID_CLOSE, ApplyMacroDialog::OnCancel) EVT_BUTTON(wxID_HELP, ApplyMacroDialog::OnHelp) END_EVENT_TABLE() ApplyMacroDialog::ApplyMacroDialog( wxWindow * parent, AudacityProject &project, bool bInherited): wxDialogWrapper(parent, wxID_ANY, MacrosPaletteTitle, wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) , mMacroCommands{ project } , mProject{ project } , mCatalog( &project ) { mAbort = false; mbExpanded = false; if( bInherited ) return; SetLabel(MacrosPaletteTitle); // Provide visual label SetName(MacrosPaletteTitle); // Provide audible label Populate(); } ApplyMacroDialog::~ApplyMacroDialog() { } void ApplyMacroDialog::Populate() { //------------------------- Main section -------------------- ShuttleGui S(this, eIsCreating); PopulateOrExchange(S); // ----------------------- End of main section -------------- // Get and validate the currently active macro mActiveMacro = gPrefs->Read(wxT("/Batch/ActiveMacro"), wxT("")); // Go populate the macros list. PopulateMacros(); Layout(); Fit(); wxSize sz = GetSize(); SetSizeHints( sz ); // Size and place window SetSize(std::min(wxSystemSettings::GetMetric(wxSYS_SCREEN_X) * 3 / 4, sz.GetWidth()), std::min(wxSystemSettings::GetMetric(wxSYS_SCREEN_Y) * 4 / 5, 400)); Center(); // Set the column size for the macros list. sz = mMacros->GetClientSize(); mMacros->SetColumnWidth(0, sz.x); } /// Defines the dialog and does data exchange with it. void ApplyMacroDialog::PopulateOrExchange(ShuttleGui &S) { /*i18n-hint: A macro is a sequence of commands that can be applied * to one or more audio files.*/ S.StartStatic(XO("Select Macro"), 1); { mMacros = S.Id(MacrosListID).Prop(1) .Style(wxSUNKEN_BORDER | wxLC_REPORT | wxLC_HRULES | wxLC_VRULES | wxLC_SINGLE_SEL) // i18n-hint: This is the heading for a column in the edit macros dialog .AddListControlReportMode( { XO("Macro") } ); } S.EndStatic(); S.StartHorizontalLay(wxEXPAND, 0); { S.AddPrompt( XXO("Apply Macro to:") ); wxButton* btn = S.Id(ApplyToProjectID) .Name(XO("Apply macro to project")) .AddButton(XXO("&Project")); #if wxUSE_ACCESSIBILITY // so that name can be set on a standard control btn->SetAccessible(safenew WindowAccessible(btn)); #endif btn = S.Id(ApplyToFilesID) .Name(XO("Apply macro to files...")) .AddButton(XXO("&Files...")); #if wxUSE_ACCESSIBILITY // so that name can be set on a standard control btn->SetAccessible(safenew WindowAccessible(btn)); #endif } S.EndHorizontalLay(); S.StartHorizontalLay(wxEXPAND, 0); { /* i18n-hint: The Expand button makes the dialog bigger, with more in it */ mResize = S.Id(ExpandID).AddButton(XXO("&Expand")); S.AddSpace( 10,10,1 ); S.AddStandardButtons( eCloseButton | eHelpButton); } S.EndHorizontalLay(); } /// This clears and updates the contents of mMacros, the list of macros. /// It has cut-and-paste code from PopulateList, and both should call /// a shared function. void ApplyMacroDialog::PopulateMacros() { auto names = mMacroCommands.GetNames(); int i; int topItem = mMacros->GetTopItem(); mMacros->DeleteAllItems(); for (i = 0; i < (int)names.size(); i++) { mMacros->InsertItem(i, names[i]); } int item = mMacros->FindItem(-1, mActiveMacro); bool bFound = item >=0; if (item == -1) { item = 0; mActiveMacro = mMacros->GetItemText(0); } // Select the name in the list...this will fire an event. mMacros->SetItemState(item, wxLIST_STATE_SELECTED | wxLIST_STATE_FOCUSED, wxLIST_STATE_SELECTED | wxLIST_STATE_FOCUSED); if( 0 <= topItem && topItem < (int)mMacros->GetItemCount()) { // Workaround for scrolling being windows only. // Try to scroll back to where we once were... mMacros->EnsureVisible( (int)mMacros->GetItemCount() -1 ); mMacros->EnsureVisible( topItem ); // And then make sure whatever is selected is still visible... if( bFound ) mMacros->EnsureVisible( item ); } } void ApplyMacroDialog::OnHelp(wxCommandEvent & WXUNUSED(event)) { const auto &page = GetHelpPageName(); HelpSystem::ShowHelp(this, page, true); } void ApplyMacroDialog::OnApplyToProject(wxCommandEvent & WXUNUSED(event)) { long item = mMacros->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); if (item == -1) { AudacityMessageBox(XO("No macro selected")); return; } ApplyMacroToProject( item ); } CommandID ApplyMacroDialog::MacroIdOfName( const wxString & MacroName ) { wxString Temp = MacroName; Temp.Replace(" ",""); Temp = wxString( "Macro_" ) + Temp; return Temp; } // Apply macro, given its ID. // Does nothing if not found, rather than returning an error. void ApplyMacroDialog::ApplyMacroToProject( const CommandID & MacroID, bool bHasGui ) { for( int i=0;iGetItemCount();i++){ wxString name = mMacros->GetItemText(i); if( MacroIdOfName( name ) == MacroID ){ ApplyMacroToProject( i, bHasGui ); return; } } } // Apply macro, given its number in the list. void ApplyMacroDialog::ApplyMacroToProject( int iMacro, bool bHasGui ) { wxString name = mMacros->GetItemText(iMacro); if( name.empty() ) return; #ifdef OPTIONAL_ACTIVITY_WINDOW wxDialogWrapper activityWin( this, wxID_ANY, GetTitle()); activityWin.SetName(); ShuttleGui S(&activityWin, eIsCreating); S.StartHorizontalLay(wxCENTER, false); { S.StartStatic( {}, false); // deliberately not translated (!) { S.SetBorder(20); S.AddFixedText(XO("Applying '%s' to current project") .Format( name ) ); } S.EndStatic(); } S.EndHorizontalLay(); activityWin.Layout(); activityWin.Fit(); activityWin.CenterOnScreen(); // Avoid overlap with progress. int x,y; activityWin.GetPosition( &x, &y ); activityWin.Move(wxMax(0,x-300), 0); activityWin.Show(); // Without this the newly created dialog may not show completely. wxYield(); #endif //Since we intend to keep this dialog open, there is no reason to hide it //and then show it again. //if( bHasGui ) // Hide(); gPrefs->Write(wxT("/Batch/ActiveMacro"), name); gPrefs->Flush(); mMacroCommands.ReadMacro(name); // The disabler must get deleted before the EndModal() call. Otherwise, // the menus on OSX will remain disabled. bool success; { #ifdef OPTIONAL_ACTIVITY_WINDOW wxWindowDisabler wd(&activityWin); #endif success = GuardedCall< bool >( [this]{ return mMacroCommands.ApplyMacro(mCatalog); } ); } if( !bHasGui ) return; Show(); Raise(); } void ApplyMacroDialog::OnApplyToFiles(wxCommandEvent & WXUNUSED(event)) { long item = mMacros->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); if (item == -1) { AudacityMessageBox( XO("No macro selected") ); return; } wxString name = mMacros->GetItemText(item); gPrefs->Write(wxT("/Batch/ActiveMacro"), name); gPrefs->Flush(); AudacityProject *project = &mProject; if (!TrackList::Get( *project ).empty()) { AudacityMessageBox( XO("Please save and close the current project first.") ); return; } // This insures that we start with an empty and temporary project ProjectFileManager::Get(*project).CloseProject(); ProjectFileManager::Get(*project).OpenProject(); auto prompt = XO("Select file(s) for batch processing..."); const auto fileTypes = Importer::Get().GetFileTypes(); auto path = FileNames::FindDefaultPath(FileNames::Operation::Open); FileDialogWrapper dlog(this, prompt, path, wxT(""), fileTypes, wxFD_OPEN | wxFD_MULTIPLE | wxRESIZE_BORDER); dlog.SetFilterIndex( Importer::SelectDefaultOpenType( fileTypes ) ); if (dlog.ShowModal() != wxID_OK) { Raise(); return; } Raise(); wxArrayString files; dlog.GetPaths(files); files.Sort(); wxDialogWrapper activityWin(this, wxID_ANY, Verbatim( GetTitle() ), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER ); activityWin.SetName(); ShuttleGui S(&activityWin, eIsCreating); wxListCtrl * fileList = NULL; S.StartVerticalLay(1); { S.StartStatic(XO("Applying..."), 1); { auto imageList = std::make_unique(9, 16); imageList->Add(wxIcon(empty9x16_xpm)); imageList->Add(wxIcon(arrow_xpm)); fileList = S.Id(CommandsListID) .Style(wxSUNKEN_BORDER | wxLC_REPORT | wxLC_HRULES | wxLC_VRULES | wxLC_SINGLE_SEL) .AddListControlReportMode( { XO("File") } ); // AssignImageList takes ownership fileList->AssignImageList(imageList.release(), wxIMAGE_LIST_SMALL); } S.EndStatic(); S.StartHorizontalLay(wxCENTER, 0); { S.Id(wxID_CANCEL).AddButton(XXO("&Cancel")); } S.EndHorizontalLay(); } S.EndVerticalLay(); int i; for (i = 0; i < (int)files.size(); i++ ) { fileList->InsertItem(i, files[i], i == 0); } // Set the column size for the files list. fileList->SetColumnWidth(0, wxLIST_AUTOSIZE); int width = wxMin( fileList->GetColumnWidth(0), 1000); wxSize sz = fileList->GetClientSize(); if (sz.GetWidth() < width ) { sz.SetWidth(width); if (sz.GetHeight() < width *0.7) sz.SetHeight(width * 0.7); fileList->SetInitialSize(sz); } activityWin.Layout(); activityWin.Fit(); activityWin.CenterOnScreen(); // Avoid overlap with progress. int x,y; activityWin.GetPosition( &x, &y ); activityWin.Move(wxMax(0,x-300), 0); activityWin.Show(); // Without this the newly created dialog may not show completely. wxYield(); // We could avoid hiding, but there are many dialogs on screen, // and hiding this one temporarily has some advantages. Hide(); mMacroCommands.ReadMacro(name); { // Move global clipboard contents aside temporarily Clipboard tempClipboard; auto &globalClipboard = Clipboard::Get(); globalClipboard.Swap(tempClipboard); auto cleanup = finally([&]{ globalClipboard.Swap(tempClipboard); }); wxWindowDisabler wd(&activityWin); for (i = 0; i < (int)files.size(); i++) { if (i > 0) { //Clear the arrow in previous item. fileList->SetItemImage(i - 1, 0, 0); } fileList->SetItemImage(i, 1, 1); fileList->EnsureVisible(i); auto success = GuardedCall< bool >([&] { ProjectFileManager::Get(*project).Import(files[i]); ProjectWindow::Get(*project).ZoomAfterImport(nullptr); SelectUtilities::DoSelectAll(*project); if (!mMacroCommands.ApplyMacro(mCatalog)) return false; if (!activityWin.IsShown() || mAbort) return false; return true; }); // Ensure project is completely reset ProjectManager::Get(*project).ResetProjectToEmpty(); // Bug2567: // Must also destroy the clipboard, to be sure sample blocks are // all freed and their ids can be reused safely in the next pass globalClipboard.Clear(); if (!success) break; } } Show(); Raise(); } void ApplyMacroDialog::OnCancel(wxCommandEvent & WXUNUSED(event)) { Hide(); } ///////////////////////////////////////////////////////////////////// #include #include "BatchCommandDialog.h" enum { AddButtonID = 10000, RemoveButtonID, RenameButtonID, RestoreButtonID, ImportButtonID, ExportButtonID, SaveButtonID, DefaultsButtonID, InsertButtonID, EditButtonID, DeleteButtonID, UpButtonID, DownButtonID, // MacrosListID 7005 // CommandsListID, 7002 // Re-Use IDs from ApplyMacroDialog. ApplyToProjectButtonID = ApplyToProjectID, ApplyToFilesButtonID = ApplyToFilesID, }; BEGIN_EVENT_TABLE(MacrosWindow, ApplyMacroDialog) EVT_LIST_ITEM_SELECTED(MacrosListID, MacrosWindow::OnMacroSelected) EVT_LIST_ITEM_SELECTED(CommandsListID, MacrosWindow::OnListSelected) EVT_LIST_BEGIN_LABEL_EDIT(MacrosListID, MacrosWindow::OnMacrosBeginEdit) EVT_LIST_END_LABEL_EDIT(MacrosListID, MacrosWindow::OnMacrosEndEdit) EVT_BUTTON(AddButtonID, MacrosWindow::OnAdd) EVT_BUTTON(RemoveButtonID, MacrosWindow::OnRemove) EVT_BUTTON(RenameButtonID, MacrosWindow::OnRename) EVT_BUTTON(RestoreButtonID, MacrosWindow::OnRestore) EVT_BUTTON(ImportButtonID, MacrosWindow::OnImport) EVT_BUTTON(ExportButtonID, MacrosWindow::OnExport) EVT_BUTTON(SaveButtonID, MacrosWindow::OnSave) EVT_BUTTON(ExpandID, MacrosWindow::OnExpand) EVT_BUTTON(ShrinkID, MacrosWindow::OnShrink) EVT_SIZE(MacrosWindow::OnSize) EVT_LIST_ITEM_ACTIVATED(CommandsListID, MacrosWindow::OnCommandActivated) EVT_BUTTON(InsertButtonID, MacrosWindow::OnInsert) EVT_BUTTON(EditButtonID, MacrosWindow::OnEditCommandParams) EVT_BUTTON(DeleteButtonID, MacrosWindow::OnDelete) EVT_BUTTON(UpButtonID, MacrosWindow::OnUp) EVT_BUTTON(DownButtonID, MacrosWindow::OnDown) EVT_BUTTON(wxID_OK, MacrosWindow::OnOK) EVT_BUTTON(wxID_CANCEL, MacrosWindow::OnCancel) EVT_BUTTON(wxID_CLOSE, MacrosWindow::OnCancel) EVT_KEY_DOWN(MacrosWindow::OnKeyDown) END_EVENT_TABLE() enum { ItemNumberColumn, ActionColumn, ParamsColumn, }; /// Constructor MacrosWindow::MacrosWindow( wxWindow * parent, AudacityProject &project, bool bExpanded): ApplyMacroDialog(parent, project, true) , mProject{ project } { mbExpanded = bExpanded; auto Title = WindowTitle(); SetLabel( Title ); // Provide visual label SetName( Title ); // Provide audible label SetTitle( Title ); mChanged = false; mSelectedCommand = 0; if( mbExpanded ) Populate(); else ApplyMacroDialog::Populate(); } MacrosWindow::~MacrosWindow() { } /// Creates the dialog and its contents. void MacrosWindow::Populate() { //------------------------- Main section -------------------- ShuttleGui S(this, eIsCreating); PopulateOrExchange(S); // ----------------------- End of main section -------------- // Get and validate the currently active macro mActiveMacro = gPrefs->Read(wxT("/Batch/ActiveMacro"), wxT("")); // Go populate the macros list. PopulateMacros(); // We have a bare list. We need to add columns and content. PopulateList(); // Layout and set minimum size of window Layout(); Fit(); SetSizeHints(GetSize()); // Size and place window SetSize(std::min(wxSystemSettings::GetMetric(wxSYS_SCREEN_X) * 3 / 4, 800), std::min(wxSystemSettings::GetMetric(wxSYS_SCREEN_Y) * 4 / 5, 400)); Center(); // Set the column size for the macros list. wxSize sz = mMacros->GetClientSize(); mMacros->SetColumnWidth(0, sz.x); // Size columns properly FitColumns(); } /// Defines the dialog and does data exchange with it. void MacrosWindow::PopulateOrExchange(ShuttleGui & S) { S.StartHorizontalLay(wxEXPAND, 1); { S.StartStatic(XO("Select Macro"),0); { S.StartHorizontalLay(wxEXPAND,1); { mMacros = S.Id(MacrosListID).Prop(1) .Style(wxSUNKEN_BORDER | wxLC_REPORT | wxLC_HRULES | wxLC_SINGLE_SEL | wxLC_EDIT_LABELS) // i18n-hint: This is the heading for a column in the edit macros dialog .AddListControlReportMode( { XO("Macro") } ); S.StartVerticalLay(wxALIGN_TOP, 0); { S.Id(AddButtonID).AddButton(XXO("&New"), wxALIGN_LEFT); mRemove = S.Id(RemoveButtonID).AddButton(XXO("Remo&ve"), wxALIGN_LEFT); mRename = S.Id(RenameButtonID).AddButton(XXO("&Rename..."), wxALIGN_LEFT); mRestore = S.Id(RestoreButtonID).AddButton(XXO("Re&store"), wxALIGN_LEFT); mImport = S.Id(ImportButtonID).AddButton(XO("I&mport..."), wxALIGN_LEFT); mExport = S.Id(ExportButtonID).AddButton(XO("E&xport..."), wxALIGN_LEFT); } S.EndVerticalLay(); } S.EndHorizontalLay(); } S.EndStatic(); S.StartStatic(XO("Edit Steps"), true); { S.StartHorizontalLay(wxEXPAND,1); { mList = S.Id(CommandsListID) .Style(wxSUNKEN_BORDER | wxLC_REPORT | wxLC_HRULES | wxLC_VRULES | wxLC_SINGLE_SEL) .AddListControlReportMode({ /* i18n-hint: This is the number of the command in the list */ { XO("Num"), wxLIST_FORMAT_RIGHT }, { XO("Command "), wxLIST_FORMAT_RIGHT }, { XO("Parameters"), wxLIST_FORMAT_LEFT } }); S.StartVerticalLay(wxALIGN_TOP, 0); { S.Id(InsertButtonID).AddButton(XXO("&Insert"), wxALIGN_LEFT); S.Id(EditButtonID).AddButton(XXO("&Edit..."), wxALIGN_LEFT); S.Id(DeleteButtonID).AddButton(XXO("De&lete"), wxALIGN_LEFT); S.Id(UpButtonID).AddButton(XXO("Move &Up"), wxALIGN_LEFT); S.Id(DownButtonID).AddButton(XXO("Move &Down"), wxALIGN_LEFT); mSave = S.Id(SaveButtonID).AddButton(XO("&Save"), wxALIGN_LEFT); mSave->Enable( mChanged ); } S.EndVerticalLay(); } S.EndHorizontalLay(); } S.EndStatic(); } S.EndHorizontalLay(); S.StartHorizontalLay(wxEXPAND, 0); { /* i18n-hint: The Shrink button makes the dialog smaller, with less in it */ mResize = S.Id(ShrinkID).AddButton(XXO("Shrin&k")); // Using variable text just to get the positioning options. S.Prop(0).AddVariableText( XO("Apply Macro to:"), false, wxALL | wxALIGN_CENTRE_VERTICAL ); wxButton* btn = S.Id(ApplyToProjectID) .Name(XO("Apply macro to project")) .AddButton(XXO("&Project")); #if wxUSE_ACCESSIBILITY // so that name can be set on a standard control btn->SetAccessible(safenew WindowAccessible(btn)); #endif btn = S.Id(ApplyToFilesID) .Name(XO("Apply macro to files...")) .AddButton(XXO("&Files...")); #if wxUSE_ACCESSIBILITY // so that name can be set on a standard control btn->SetAccessible(safenew WindowAccessible(btn)); #endif S.AddSpace( 10,10,1 ); // Bug 2524 OK button does much the same as cancel, so remove it. // OnCancel prompts you if there has been a change. // OnOK saves without prompting. // That difference is too slight to merit a button, and with the OK // button, people might expect the dialog to apply the macro too. S.AddStandardButtons( /*eOkButton |*/ eCloseButton | eHelpButton); } S.EndHorizontalLay(); return; } /// This clears and updates the contents of mList, the commands for the current macro. void MacrosWindow::PopulateList() { int topItem = mList->GetTopItem(); mList->DeleteAllItems(); for (int i = 0; i < mMacroCommands.GetCount(); i++) { AddItem(mMacroCommands.GetCommand(i), mMacroCommands.GetParams(i)); } /*i18n-hint: This is the last item in a list.*/ AddItem(_("- END -"), wxT("")); // Select the name in the list...this will fire an event. if (mSelectedCommand >= (int)mList->GetItemCount()) { mSelectedCommand = 0; } mList->SetItemState(mSelectedCommand, wxLIST_STATE_SELECTED | wxLIST_STATE_FOCUSED, wxLIST_STATE_SELECTED | wxLIST_STATE_FOCUSED); if( 0 <= topItem && topItem < (int)mList->GetItemCount()) { // Workaround for scrolling being windows only. // Try to scroll back to where we once were... mList->EnsureVisible( (int)mList->GetItemCount() -1 ); mList->EnsureVisible( topItem ); // And then make sure whatever is selected is still visible... if (mSelectedCommand >= 0) { mList->EnsureVisible( mSelectedCommand ); } } } /// Add one item into mList void MacrosWindow::AddItem(const CommandID &Action, const wxString &Params) { auto entry = mCatalog.ByCommandId(Action); auto friendlyName = entry != mCatalog.end() ? entry->name.StrippedTranslation() : // uh oh, using GET to expose an internal name to the user! // in default of any better friendly name Action.GET(); int i = mList->GetItemCount(); mList->InsertItem(i, wxString::Format(wxT(" %02i"), i + 1)); mList->SetItem(i, ActionColumn, friendlyName ); mList->SetItem(i, ParamsColumn, Params ); } void MacrosWindow::UpdateMenus() { // OK even on mac, as dialog is modal. auto p = &mProject; MenuManager::Get(*p).RebuildMenuBar(*p); } void MacrosWindow::UpdateDisplay( bool bExpanded ) { // If we failed to save changes, we abandon the attempt to // change the expand/shrink state of the GUI. if( !SaveChanges() ) return; mbExpanded = bExpanded; mChanged = false; // if we try to access the about to be destroyed mSave button // inappropriately, we need to crash rather than (sometimes) silently // succeed. mSave = nullptr; DestroyChildren(); SetSizer( nullptr ); mSelectedCommand = 0; SetMinSize( wxSize( 200,200 )); // Get and set position for optical stability. // Expanded and shrunk dialogs 'stay where they were'. // That's OK , and what we want, even if we exapnd off-screen. // We won't shrink to being off-screen, since the shrink button // was clicked, so must have been on screen. wxPoint p = GetPosition( ); if( mbExpanded ) Populate(); else ApplyMacroDialog::Populate(); SetPosition( p ); mResize->SetFocus(); auto Title = WindowTitle(); SetLabel( Title ); // Provide visual label SetName( Title ); // Provide audible label SetTitle( Title ); } void MacrosWindow::OnExpand(wxCommandEvent &WXUNUSED(event)) { UpdateDisplay( true );} void MacrosWindow::OnShrink(wxCommandEvent &WXUNUSED(event)) { if( ChangeOK() ) UpdateDisplay( false ); } bool MacrosWindow::ChangeOK() { if (mChanged) { int id; auto title = XO("%s changed").Format( mActiveMacro ); auto msg = XO("Do you want to save the changes?"); id = AudacityMessageBox( msg, title, wxYES_NO | wxCANCEL); if (id == wxCANCEL) { return false; } if (id == wxYES) { if (!mMacroCommands.WriteMacro(mActiveMacro)) { return false; } } mChanged = false; mSave->Enable( mChanged ); } return true; } /// An item in the macros list has been selected. void MacrosWindow::OnMacroSelected(wxListEvent & event) { if (!ChangeOK()) { event.Veto(); return; } int item = event.GetIndex(); mActiveMacro = mMacros->GetItemText(item); ShowActiveMacro(); } void MacrosWindow::ShowActiveMacro() { mMacroCommands.ReadMacro(mActiveMacro); if( !mbExpanded ) return; if (mMacroCommands.IsFixed(mActiveMacro)) { mRemove->Disable(); mRename->Disable(); mRestore->Enable(); } else { mRemove->Enable(); mRename->Enable(); mRestore->Disable(); } PopulateList(); } /// An item in the macros list has been selected. void MacrosWindow::OnListSelected(wxListEvent & WXUNUSED(event)) { FitColumns(); } /// The window has been resized. void MacrosWindow::OnSize(wxSizeEvent & WXUNUSED(event)) { // Refresh the layout and re-fit the columns. Layout(); if( !mbExpanded ) return; FitColumns(); } void MacrosWindow::FitColumns() { #if defined(__WXMAC__) // wxMac uses a hard coded width of 150 when wxLIST_AUTOSIZE_USEHEADER // is specified, so we calculate the width ourselves. This method may // work equally well on other platforms. for (size_t c = 0; c < mList->GetColumnCount(); c++) { wxListItem info; int width; mList->SetColumnWidth(c, wxLIST_AUTOSIZE); info.Clear(); info.SetId(c); info.SetMask(wxLIST_MASK_TEXT | wxLIST_MASK_WIDTH); mList->GetColumn(c, info); mList->GetTextExtent(info.GetText(), &width, NULL); width += 2 * 4; // 2 * kItemPadding - see listctrl_mac.cpp width += 16; // kIconWidth - see listctrl_mac.cpp mList->SetColumnWidth(c, wxMax(width, mList->GetColumnWidth(c))); } // Looks strange, but it forces the horizontal scrollbar to get // drawn. If not done, strange column sizing can occur if the // user attempts to resize the columns. mList->SetClientSize(mList->GetClientSize()); #else mList->SetColumnWidth(0, wxLIST_AUTOSIZE_USEHEADER); mList->SetColumnWidth(1, wxLIST_AUTOSIZE_USEHEADER); mList->SetColumnWidth(2, wxLIST_AUTOSIZE); #endif int bestfit = mList->GetColumnWidth(2); int clientsize = mList->GetClientSize().GetWidth(); int col0 = mList->GetColumnWidth(0); int col1 = mList->GetColumnWidth(1); bestfit = (bestfit > clientsize-col0-col1)? bestfit : clientsize-col0-col1; mList->SetColumnWidth(2, bestfit); } /// void MacrosWindow::OnMacrosBeginEdit(wxListEvent &event) { int itemNo = event.GetIndex(); wxString macro = mMacros->GetItemText(itemNo); if (mMacroCommands.IsFixed(macro)) { wxBell(); event.Veto(); } if( mMacroBeingRenamed.IsEmpty()) mMacroBeingRenamed = macro; } /// void MacrosWindow::OnMacrosEndEdit(wxListEvent &event) { if (event.IsEditCancelled()) { mMacroBeingRenamed = ""; return; } if( mMacroBeingRenamed.IsEmpty()) return; wxString newname = event.GetLabel(); mMacroCommands.RenameMacro(mMacroBeingRenamed, newname); if( mMacroBeingRenamed == mActiveMacro ) mActiveMacro = newname; mMacroBeingRenamed=""; PopulateMacros(); UpdateMenus(); event.Veto(); } /// void MacrosWindow::OnAdd(wxCommandEvent & WXUNUSED(event)) { // Similar to Bug 2284 we may need to save a changed macro. if (!ChangeOK()) { return; } while (true) { AudacityTextEntryDialog d(this, XO("Enter name of new macro"), XO("Name of new macro")); d.SetName(d.GetTitle()); wxString name; if (d.ShowModal() == wxID_CANCEL) { Raise(); return; } Raise(); name = d.GetValue().Strip(wxString::both); if (name.length() == 0) { AudacityMessageBox( XO("Name must not be blank"), WindowTitle(), wxOK | wxICON_ERROR, this); continue; } if (name.Contains(wxFILE_SEP_PATH) || name.Contains(wxFILE_SEP_PATH_UNIX)) { AudacityMessageBox( /*i18n-hint: The %c will be replaced with 'forbidden characters', like '/' and '\'.*/ XO("Names may not contain '%c' and '%c'") .Format(wxFILE_SEP_PATH, wxFILE_SEP_PATH_UNIX), WindowTitle(), wxOK | wxICON_ERROR, this); continue; } mMacroCommands.AddMacro(name); mActiveMacro = name; PopulateMacros(); UpdateMenus(); break; } } /// void MacrosWindow::OnRemove(wxCommandEvent & WXUNUSED(event)) { long item = mMacros->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); if (item == -1) { return; } wxString name = mMacros->GetItemText(item); AudacityMessageDialog m( this, /*i18n-hint: %s will be replaced by the name of a file.*/ XO("Are you sure you want to delete %s?").Format( name ), Verbatim( GetTitle() ), wxYES_NO | wxICON_QUESTION ); if (m.ShowModal() == wxID_NO) { Raise(); return; } Raise(); mMacroCommands.DeleteMacro(name); item++; if (item >= (mMacros->GetItemCount() - 1) && item >= 0) { item--; } // Bug 2284. The macro we have just removed might have been // changed. Since we've just deleted the macro, we should // forget about that change. mChanged = false; mSave->Enable( mChanged ); mActiveMacro = mMacros->GetItemText(item); PopulateMacros(); UpdateMenus(); } /// void MacrosWindow::OnRename(wxCommandEvent & WXUNUSED(event)) { long item = mMacros->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); if (item == -1) { return; } mMacros->EditLabel(item); UpdateMenus(); } /// Reset a built in macro. void MacrosWindow::OnRestore(wxCommandEvent & WXUNUSED(event)) { mMacroCommands.RestoreMacro(mActiveMacro); mChanged = true; mSave->Enable( mChanged ); PopulateList(); } /// void MacrosWindow::OnImport(wxCommandEvent & WXUNUSED(event)) { if (!ChangeOK()) { return; } long item = mMacros->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); if (item == -1) { return; } wxString name = mMacros->GetItemText(item); name = mMacroCommands.ReadMacro({}, this); if (name == wxEmptyString) { return; } mActiveMacro = name; PopulateMacros(); UpdateMenus(); } /// void MacrosWindow::OnExport(wxCommandEvent & WXUNUSED(event)) { long item = mMacros->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); if (item == -1) { return; } mMacroCommands.WriteMacro(mMacros->GetItemText(item), this); } void MacrosWindow::OnSave(wxCommandEvent & WXUNUSED(event)) { SaveChanges(); } /// An item in the list has been selected. /// Bring up a dialog to allow its parameters to be edited. void MacrosWindow::OnCommandActivated(wxListEvent & WXUNUSED(event)) { wxCommandEvent dummy; OnEditCommandParams( dummy ); } /// void MacrosWindow::OnInsert(wxCommandEvent & WXUNUSED(event)) { long item = mList->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); if (item == -1) { item = mList->GetItemCount()-1; } InsertCommandAt( item ); } void MacrosWindow::InsertCommandAt(int item) { if (item == -1) { return; } MacroCommandDialog d(this, wxID_ANY, mProject); if (!d.ShowModal()) { Raise(); return; } Raise(); if(!d.mSelectedCommand.empty()) { mMacroCommands.AddToMacro(d.mSelectedCommand, d.mSelectedParameters, item); mChanged = true; mSave->Enable( mChanged ); mSelectedCommand = item + 1; PopulateList(); } } void MacrosWindow::OnEditCommandParams(wxCommandEvent & WXUNUSED(event)) { int item = mList->GetNextItem( -1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED ); // LAST command in list is END. // If nothing selected, add at END. // If END selected, add at END. // When adding at end we use InsertCommandAt, so that a new command // can be chosen. int lastItem = mList->GetItemCount()-1; if( (item<0) || (item+1) == mList->GetItemCount() ) { InsertCommandAt( lastItem ); return; } // Just edit the parameters, and not the command. auto command = mMacroCommands.GetCommand(item); wxString params = mMacroCommands.GetParams(item); wxString oldParams = params; params = MacroCommands::PromptForParamsFor(command, params, *this).Trim(); Raise(); if (oldParams == params) return; // They did not actually make any changes.. mMacroCommands.DeleteFromMacro(item); mMacroCommands.AddToMacro(command, params, item); mChanged = true; mSave->Enable( mChanged ); mSelectedCommand = item; PopulateList(); } /// void MacrosWindow::OnDelete(wxCommandEvent & WXUNUSED(event)) { long item = mList->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); if (item == -1 || item + 1 == mList->GetItemCount()) { return; } mMacroCommands.DeleteFromMacro(item); mChanged = true; mSave->Enable( mChanged ); if (item >= (mList->GetItemCount() - 2) && item >= 0) { item--; } mSelectedCommand = item; PopulateList(); } /// void MacrosWindow::OnUp(wxCommandEvent & WXUNUSED(event)) { long item = mList->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); if (item == -1 || item == 0 || item + 1 == mList->GetItemCount()) { return; } mMacroCommands.AddToMacro(mMacroCommands.GetCommand(item), mMacroCommands.GetParams(item), item - 1); mMacroCommands.DeleteFromMacro(item + 1); mChanged = true; mSave->Enable( mChanged ); mSelectedCommand = item - 1; PopulateList(); } /// void MacrosWindow::OnDown(wxCommandEvent & WXUNUSED(event)) { long item = mList->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); if (item == -1 || item + 2 >= mList->GetItemCount()) { return; } mMacroCommands.AddToMacro(mMacroCommands.GetCommand(item), mMacroCommands.GetParams(item), item + 2); mMacroCommands.DeleteFromMacro(item); mChanged = true; mSave->Enable( mChanged ); mSelectedCommand = item + 1; PopulateList(); } void MacrosWindow::OnApplyToProject(wxCommandEvent & event) { if( !SaveChanges() ) return; ApplyMacroDialog::OnApplyToProject( event ); } void MacrosWindow::OnApplyToFiles(wxCommandEvent & event) { if( !SaveChanges() ) return; ApplyMacroDialog::OnApplyToFiles( event ); } bool MacrosWindow::SaveChanges(){ gPrefs->Write(wxT("/Batch/ActiveMacro"), mActiveMacro); gPrefs->Flush(); if (mChanged) { if (!mMacroCommands.WriteMacro(mActiveMacro)) { return false; } } mChanged = false; if( mSave ) mSave->Enable( mChanged ); return true; } /// Send changed values back to Prefs, and update Audacity. void MacrosWindow::OnOK(wxCommandEvent & WXUNUSED(event)) { if( !SaveChanges() ) return; Hide(); //EndModal(true); } /// void MacrosWindow::OnCancel(wxCommandEvent &WXUNUSED(event)) { bool bWasChanged = mChanged; if (!ChangeOK()) { return; } // If we've rejected a change, we need to restore the display // of the active macro. // That's because next time we open this dialog we want to see the // unedited macro. if( bWasChanged ) ShowActiveMacro(); Hide(); } /// void MacrosWindow::OnKeyDown(wxKeyEvent &event) { if (event.GetKeyCode() == WXK_DELETE) { wxLogDebug(wxT("wxKeyEvent")); } event.Skip(); } TranslatableString MacrosWindow::WindowTitle() const { return mbExpanded ? ManageMacrosTitle : MacrosPaletteTitle; } // PrefsListener implementation void MacrosWindow::UpdatePrefs() { UpdateDisplay(mbExpanded); }