diff --git a/mac/Audacity.xcodeproj/project.pbxproj b/mac/Audacity.xcodeproj/project.pbxproj index b490f2dbc..bd9f864b3 100644 --- a/mac/Audacity.xcodeproj/project.pbxproj +++ b/mac/Audacity.xcodeproj/project.pbxproj @@ -8592,6 +8592,7 @@ INFOPLIST_PREPROCESS = YES; KEEP_PRIVATE_EXTERNS = YES; ONLY_LINK_ESSENTIAL_SYMBOLS = YES; + ON_DEMAND_RESOURCES_INITIAL_INSTALL_TAGS = ""; OTHER_LDFLAGS = ( "-Xlinker", "-headerpad_max_install_names", @@ -8916,6 +8917,7 @@ INFOPLIST_PREPROCESS = YES; KEEP_PRIVATE_EXTERNS = YES; ONLY_LINK_ESSENTIAL_SYMBOLS = YES; + ON_DEMAND_RESOURCES_INITIAL_INSTALL_TAGS = ""; OTHER_LDFLAGS = ( "-Xlinker", "-headerpad_max_install_names", diff --git a/src/Menus.cpp b/src/Menus.cpp index fa413cd43..288972eda 100644 --- a/src/Menus.cpp +++ b/src/Menus.cpp @@ -84,7 +84,6 @@ menu items. #ifdef USE_MIDI #include "NoteTrack.h" #endif // USE_MIDI -#include "Tags.h" #include "TimeTrack.h" #include "Mix.h" #include "AboutDialog.h" @@ -113,7 +112,6 @@ menu items. #include "TimeDialog.h" #include "TimerRecordDialog.h" #include "SoundActivatedRecord.h" -#include "LabelDialog.h" #include "SplashDialog.h" #include "widgets/HelpSystem.h" @@ -530,8 +528,10 @@ static CommandHandlerObject &findMenuCommandHandler(AudacityProject &project) MenuTable::BaseItemPtr FileMenu( AudacityProject& ); -namespace { MenuTable::BaseItemPtr EditMenu( AudacityProject& ); +MenuTable::BaseItemPtr ExtraEditMenu( AudacityProject & ); + +namespace { MenuTable::BaseItemPtr SelectMenu( AudacityProject& ); MenuTable::BaseItemPtr ToolbarsMenu( AudacityProject& ); MenuTable::BaseItemPtr ViewMenu( AudacityProject& ); @@ -548,7 +548,6 @@ MenuTable::BaseItemPtr ExtraMenu( AudacityProject& ); MenuTable::BaseItemPtr ExtraTransportMenu( AudacityProject & ); MenuTable::BaseItemPtr ExtraToolsMenu( AudacityProject & ); MenuTable::BaseItemPtr ExtraMixerMenu( AudacityProject & ); -MenuTable::BaseItemPtr ExtraEditMenu( AudacityProject & ); MenuTable::BaseItemPtr ExtraPlayAtSpeedMenu( AudacityProject & ); MenuTable::BaseItemPtr ExtraSeekMenu( AudacityProject & ); MenuTable::BaseItemPtr ExtraDeviceMenu( AudacityProject & ); @@ -605,220 +604,6 @@ static const auto menuTree = MenuTable::Items( namespace { -MenuTable::BaseItemPtr LabelEditMenus( AudacityProject &project ) -{ - using namespace MenuTable; - using Options = CommandManager::Options; - - static const auto checkOff = Options{}.CheckState( false ); - - constexpr auto NotBusyLabelsAndWaveFlags = - AudioIONotBusyFlag | - LabelsSelectedFlag | WaveTracksExistFlag | TimeSelectedFlag; - - // Returns TWO menus. - return Items ( - - Menu( _("&Labels"), - Command( wxT("EditLabels"), XXO("&Edit Labels..."), FN(OnEditLabels), - AudioIONotBusyFlag ), - - Separator(), - - Command( wxT("AddLabel"), XXO("Add Label at &Selection"), - FN(OnAddLabel), AlwaysEnabledFlag, wxT("Ctrl+B") ), - Command( wxT("AddLabelPlaying"), - XXO("Add Label at &Playback Position"), - FN(OnAddLabelPlaying), AudioIOBusyFlag, -#ifdef __WXMAC__ - wxT("Ctrl+.") -#else - wxT("Ctrl+M") -#endif - ), - Command( wxT("PasteNewLabel"), XXO("Paste Te&xt to New Label"), - FN(OnPasteNewLabel), - AudioIONotBusyFlag, wxT("Ctrl+Alt+V") ), - - Separator(), - - Command( wxT("TypeToCreateLabel"), - XXO("&Type to Create a Label (on/off)"), - FN(OnToggleTypeToCreateLabel), AlwaysEnabledFlag, checkOff ) - ), // first menu - - ///////////////////////////////////////////////////////////////////////////// - - Menu( _("La&beled Audio"), - /* i18n-hint: (verb)*/ - Command( wxT("CutLabels"), XXO("&Cut"), FN(OnCutLabels), - AudioIONotBusyFlag | LabelsSelectedFlag | WaveTracksExistFlag | - TimeSelectedFlag | IsNotSyncLockedFlag, - Options{ wxT("Alt+X"), _("Label Cut") } ), - Command( wxT("DeleteLabels"), XXO("&Delete"), FN(OnDeleteLabels), - AudioIONotBusyFlag | LabelsSelectedFlag | WaveTracksExistFlag | - TimeSelectedFlag | IsNotSyncLockedFlag, - Options{ wxT("Alt+K"), _("Label Delete") } ), - - Separator(), - - /* i18n-hint: (verb) A special way to cut out a piece of audio*/ - Command( wxT("SplitCutLabels"), XXO("&Split Cut"), - FN(OnSplitCutLabels), NotBusyLabelsAndWaveFlags, - Options{ wxT("Alt+Shift+X"), _("Label Split Cut") } ), - Command( wxT("SplitDeleteLabels"), XXO("Sp&lit Delete"), - FN(OnSplitDeleteLabels), NotBusyLabelsAndWaveFlags, - Options{ wxT("Alt+Shift+K"), _("Label Split Delete") } ), - - Separator(), - - Command( wxT("SilenceLabels"), XXO("Silence &Audio"), - FN(OnSilenceLabels), NotBusyLabelsAndWaveFlags, - Options{ wxT("Alt+L"), _("Label Silence") } ), - /* i18n-hint: (verb)*/ - Command( wxT("CopyLabels"), XXO("Co&py"), FN(OnCopyLabels), - NotBusyLabelsAndWaveFlags, - Options{ wxT("Alt+Shift+C"), _("Label Copy") } ), - - Separator(), - - /* i18n-hint: (verb)*/ - Command( wxT("SplitLabels"), XXO("Spli&t"), FN(OnSplitLabels), - AudioIONotBusyFlag | LabelsSelectedFlag | WaveTracksExistFlag, - Options{ wxT("Alt+I"), _("Label Split") } ), - /* i18n-hint: (verb)*/ - Command( wxT("JoinLabels"), XXO("&Join"), FN(OnJoinLabels), - NotBusyLabelsAndWaveFlags, - Options{ wxT("Alt+J"), _("Label Join") } ), - Command( wxT("DisjoinLabels"), XXO("Detac&h at Silences"), - FN(OnDisjoinLabels), NotBusyLabelsAndWaveFlags, - wxT("Alt+Shift+J") ) - ) // second menu - ); // Two menus -} - -MenuTable::BaseItemPtr EditMenu( AudacityProject &project ) -{ - using namespace MenuTable; - using Options = CommandManager::Options; - - constexpr auto NotBusyTimeAndTracksFlags = - AudioIONotBusyFlag | TimeSelectedFlag | TracksSelectedFlag; - - // The default shortcut key for Redo is different on different platforms. - static constexpr auto redoKey = -#ifdef __WXMSW__ - wxT("Ctrl+Y") -#else - wxT("Ctrl+Shift+Z") -#endif - ; - - // The default shortcut key for Preferences is different on different - // platforms. - static constexpr auto prefKey = -#ifdef __WXMAC__ - wxT("Ctrl+,") -#else - wxT("Ctrl+P") -#endif - ; - - return Menu( _("&Edit"), - Command( wxT("Undo"), XXO("&Undo"), FN(OnUndo), - AudioIONotBusyFlag | UndoAvailableFlag, wxT("Ctrl+Z") ), - - Command( wxT("Redo"), XXO("&Redo"), FN(OnRedo), - AudioIONotBusyFlag | RedoAvailableFlag, redoKey ), - - Special( [](AudacityProject &project, wxMenu&) { - // Change names in the CommandManager as a side-effect - MenuManager::ModifyUndoMenuItems(project); - }), - - Separator(), - - // Basic Edit commands - /* i18n-hint: (verb)*/ - Command( wxT("Cut"), XXO("Cu&t"), FN(OnCut), - AudioIONotBusyFlag | CutCopyAvailableFlag | NoAutoSelect, - Options{ wxT("Ctrl+X") } - .Mask( AudioIONotBusyFlag | CutCopyAvailableFlag ) ), - Command( wxT("Delete"), XXO("&Delete"), FN(OnDelete), - AudioIONotBusyFlag | NoAutoSelect, - Options{ wxT("Ctrl+K") } - .Mask( AudioIONotBusyFlag ) ), - /* i18n-hint: (verb)*/ - Command( wxT("Copy"), XXO("&Copy"), FN(OnCopy), - AudioIONotBusyFlag | CutCopyAvailableFlag, wxT("Ctrl+C") ), - /* i18n-hint: (verb)*/ - Command( wxT("Paste"), XXO("&Paste"), FN(OnPaste), - AudioIONotBusyFlag, wxT("Ctrl+V") ), - /* i18n-hint: (verb)*/ - Command( wxT("Duplicate"), XXO("Duplic&ate"), FN(OnDuplicate), - NotBusyTimeAndTracksFlags, wxT("Ctrl+D") ), - - Separator(), - - Menu( _("R&emove Special"), - /* i18n-hint: (verb) Do a special kind of cut*/ - Command( wxT("SplitCut"), XXO("Spl&it Cut"), FN(OnSplitCut), - NotBusyTimeAndTracksFlags, wxT("Ctrl+Alt+X") ), - /* i18n-hint: (verb) Do a special kind of DELETE*/ - Command( wxT("SplitDelete"), XXO("Split D&elete"), FN(OnSplitDelete), - NotBusyTimeAndTracksFlags, wxT("Ctrl+Alt+K") ), - - Separator(), - - /* i18n-hint: (verb)*/ - Command( wxT("Silence"), XXO("Silence Audi&o"), FN(OnSilence), - AudioIONotBusyFlag | TimeSelectedFlag | AudioTracksSelectedFlag, - wxT("Ctrl+L") ), - /* i18n-hint: (verb)*/ - Command( wxT("Trim"), XXO("Tri&m Audio"), FN(OnTrim), - AudioIONotBusyFlag | TimeSelectedFlag | AudioTracksSelectedFlag, - wxT("Ctrl+T") ) - ), - - Separator(), - - ///////////////////////////////////////////////////////////////////////////// - - Menu( _("Clip B&oundaries"), - /* i18n-hint: (verb) It's an item on a menu. */ - Command( wxT("Split"), XXO("Sp&lit"), FN(OnSplit), - AudioIONotBusyFlag | WaveTracksSelectedFlag, wxT("Ctrl+I") ), - Command( wxT("SplitNew"), XXO("Split Ne&w"), FN(OnSplitNew), - AudioIONotBusyFlag | TimeSelectedFlag | WaveTracksSelectedFlag, - wxT("Ctrl+Alt+I") ), - - Separator(), - - /* i18n-hint: (verb)*/ - Command( wxT("Join"), XXO("&Join"), FN(OnJoin), - NotBusyTimeAndTracksFlags, wxT("Ctrl+J") ), - Command( wxT("Disjoin"), XXO("Detac&h at Silences"), FN(OnDisjoin), - NotBusyTimeAndTracksFlags, wxT("Ctrl+Alt+J") ) - ), - - ///////////////////////////////////////////////////////////////////////////// - - LabelEditMenus, - - Command( wxT("EditMetaData"), XXO("&Metadata..."), FN(OnEditMetadata), - AudioIONotBusyFlag ), - - ///////////////////////////////////////////////////////////////////////////// - -#ifndef __WXMAC__ - Separator(), -#endif - - Command( wxT("Preferences"), XXO("Pre&ferences..."), FN(OnPreferences), - AudioIONotBusyFlag, prefKey ) - ); -} - MenuTable::BaseItemPtr ClipSelectMenu( AudacityProject& ) { using namespace MenuTable; @@ -1745,22 +1530,6 @@ MenuTable::BaseItemPtr ExtraMixerMenu( AudacityProject & ) ); } -MenuTable::BaseItemPtr ExtraEditMenu( AudacityProject & ) -{ - using namespace MenuTable; - using Options = CommandManager::Options; - constexpr auto flags = - AudioIONotBusyFlag | TracksSelectedFlag | TimeSelectedFlag; - return Menu( _("&Edit"), - Command( wxT("DeleteKey"), XXO("&Delete Key"), FN(OnDelete), - (flags | NoAutoSelect), - Options{ wxT("Backspace") }.Mask( flags ) ), - Command( wxT("DeleteKey2"), XXO("Delete Key&2"), FN(OnDelete), - (flags | NoAutoSelect), - Options{ wxT("Delete") }.Mask( flags ) ) - ); -} - MenuTable::BaseItemPtr ExtraPlayAtSpeedMenu( AudacityProject & ) { using namespace MenuTable; @@ -5416,1263 +5185,10 @@ void MenuCommandHandler::OnCheckDependencies(const CommandContext &context) ::ShowDependencyDialogIfNeeded(&project, false); } -void MenuCommandHandler::OnPreferences(const CommandContext &context) -{ - auto &project = context.project; - - GlobalPrefsDialog dialog(&project /* parent */ ); - - if( ScreenshotCommand::MayCapture( &dialog ) ) - return; - - if (!dialog.ShowModal()) { - // Canceled - return; - } - - // LL: Moved from PrefsDialog since wxWidgets on OSX can't deal with - // rebuilding the menus while the PrefsDialog is still in the modal - // state. - for (size_t i = 0; i < gAudacityProjects.size(); i++) { - AudacityProject *p = gAudacityProjects[i].get(); - - GetMenuManager(*p).RebuildMenuBar(*p); - p->RebuildOtherMenus(); -// TODO: The comment below suggests this workaround is obsolete. -#if defined(__WXGTK__) - // Workaround for: - // - // http://bugzilla.audacityteam.org/show_bug.cgi?id=458 - // - // This workaround should be removed when Audacity updates to wxWidgets 3.x which has a fix. - wxRect r = p->GetRect(); - p->SetSize(wxSize(1,1)); - p->SetSize(r.GetSize()); -#endif - } -} - -#include "./prefs/SpectrogramSettings.h" -#include "./prefs/WaveformSettings.h" -void MenuCommandHandler::OnReloadPreferences(const CommandContext &context ) -{ - auto &project = context.project; - - { - SpectrogramSettings::defaults().LoadPrefs(); - WaveformSettings::defaults().LoadPrefs(); - - GlobalPrefsDialog dialog(&project /* parent */ ); - wxCommandEvent Evt; - //dialog.Show(); - dialog.OnOK(Evt); - } - - // LL: Moved from PrefsDialog since wxWidgets on OSX can't deal with - // rebuilding the menus while the PrefsDialog is still in the modal - // state. - for (size_t i = 0; i < gAudacityProjects.size(); i++) { - AudacityProject *p = gAudacityProjects[i].get(); - - GetMenuManager(*p).RebuildMenuBar(*p); - p->RebuildOtherMenus(); -// TODO: The comment below suggests this workaround is obsolete. -#if defined(__WXGTK__) - // Workaround for: - // - // http://bugzilla.audacityteam.org/show_bug.cgi?id=458 - // - // This workaround should be removed when Audacity updates to wxWidgets 3.x which has a fix. - wxRect r = p->GetRect(); - p->SetSize(wxSize(1,1)); - p->SetSize(r.GetSize()); -#endif - } -} - // // Edit Menu // -void MenuCommandHandler::OnUndo(const CommandContext &context) -{ - auto &project = context.project; - auto trackPanel = project.GetTrackPanel(); - auto &undoManager = *project.GetUndoManager(); - auto &selectedRegion = project.GetViewInfo().selectedRegion; - auto mixerBoard = project.GetMixerBoard(); - auto historyWindow = project.GetHistoryWindow(); - - if (!project.UndoAvailable()) { - AudacityMessageBox(_("Nothing to undo")); - return; - } - - // can't undo while dragging - if (trackPanel->IsMouseCaptured()) { - return; - } - - const UndoState &state = undoManager.Undo(&selectedRegion); - project.PopState(state); - - trackPanel->EnsureVisible(trackPanel->GetFirstSelectedTrack()); - - project.RedrawProject(); - - if (historyWindow) - historyWindow->UpdateDisplay(); - - if (mixerBoard) - // Mixer board may need to change for selection state and pan/gain - mixerBoard->Refresh(); - - MenuManager::ModifyUndoMenuItems(project); -} - -void MenuCommandHandler::OnRedo(const CommandContext &context) -{ - auto &project = context.project; - auto trackPanel = project.GetTrackPanel(); - auto &undoManager = *project.GetUndoManager(); - auto &selectedRegion = project.GetViewInfo().selectedRegion; - auto mixerBoard = project.GetMixerBoard(); - auto historyWindow = project.GetHistoryWindow(); - - if (!project.RedoAvailable()) { - AudacityMessageBox(_("Nothing to redo")); - return; - } - // Can't redo whilst dragging - if (trackPanel->IsMouseCaptured()) { - return; - } - - const UndoState &state = undoManager.Redo(&selectedRegion); - project.PopState(state); - - trackPanel->EnsureVisible(trackPanel->GetFirstSelectedTrack()); - - project.RedrawProject(); - - if (historyWindow) - historyWindow->UpdateDisplay(); - - if (mixerBoard) - // Mixer board may need to change for selection state and pan/gain - mixerBoard->Refresh(); - - MenuManager::ModifyUndoMenuItems(project); -} - -void MenuCommandHandler::FinishCopy - (const Track *n, Track::Holder &&dest, TrackList &list) -{ - Track::FinishCopy( n, dest.get() ); - if (dest) - list.Add(std::move(dest)); -} - -void MenuCommandHandler::OnCut(const CommandContext &context) -{ - auto &project = context.project; - auto tracks = project.GetTracks(); - auto trackPanel = project.GetTrackPanel(); - auto &selectedRegion = project.GetViewInfo().selectedRegion; - auto ruler = project.GetRulerPanel(); - auto historyWindow = project.GetHistoryWindow(); - - // This doesn't handle cutting labels, it handles - // cutting the _text_ inside of labels, i.e. if you're - // in the middle of editing the label text and select "Cut". - - for (auto lt : tracks->Selected< LabelTrack >()) { - if (lt->CutSelectedText()) { - trackPanel->Refresh(false); - return; - } - } - - AudacityProject::ClearClipboard(); - - auto pNewClipboard = TrackList::Create(); - auto &newClipboard = *pNewClipboard; - - tracks->Selected().Visit( -#if defined(USE_MIDI) - [&](NoteTrack *n) { - // Since portsmf has a built-in cut operator, we use that instead - auto dest = n->Cut(selectedRegion.t0(), - selectedRegion.t1()); - FinishCopy(n, std::move(dest), newClipboard); - }, -#endif - [&](Track *n) { - auto dest = n->Copy(selectedRegion.t0(), - selectedRegion.t1()); - FinishCopy(n, std::move(dest), newClipboard); - } - ); - - // Survived possibility of exceptions. Commit changes to the clipboard now. - newClipboard.Swap(*AudacityProject::msClipboard); - - // Proceed to change the project. If this throws, the project will be - // rolled back by the top level handler. - - (tracks->Any() + &Track::IsSelectedOrSyncLockSelected).Visit( -#if defined(USE_MIDI) - [](NoteTrack*) { - //if NoteTrack, it was cut, so do not clear anything - - // PRL: But what if it was sync lock selected only, not selected? - }, -#endif - [&](WaveTrack *wt, const Track::Fallthrough &fallthrough) { - if (gPrefs->Read(wxT("/GUI/EnableCutLines"), (long)0)) { - wt->ClearAndAddCutLine( - selectedRegion.t0(), - selectedRegion.t1()); - } - else - fallthrough(); - }, - [&](Track *n) { - n->Clear(selectedRegion.t0(), - selectedRegion.t1()); - } - ); - - AudacityProject::msClipT0 = selectedRegion.t0(); - AudacityProject::msClipT1 = selectedRegion.t1(); - AudacityProject::msClipProject = &project; - - selectedRegion.collapseToT0(); - - project.PushState(_("Cut to the clipboard"), _("Cut")); - - // Bug 1663 - //mRuler->ClearPlayRegion(); - ruler->DrawOverlays( true ); - - project.RedrawProject(); - - if (historyWindow) - historyWindow->UpdateDisplay(); -} - - -void MenuCommandHandler::OnSplitCut(const CommandContext &context) -{ - auto &project = context.project; - auto tracks = project.GetTracks(); - auto &selectedRegion = project.GetViewInfo().selectedRegion; - auto historyWindow = project.GetHistoryWindow(); - - AudacityProject::ClearClipboard(); - - auto pNewClipboard = TrackList::Create(); - auto &newClipboard = *pNewClipboard; - - Track::Holder dest; - - tracks->Selected().Visit( - [&](WaveTrack *n) { - dest = n->SplitCut( - selectedRegion.t0(), - selectedRegion.t1()); - if (dest) - FinishCopy(n, std::move(dest), newClipboard); - }, - [&](Track *n) { - dest = n->Copy(selectedRegion.t0(), - selectedRegion.t1()); - n->Silence(selectedRegion.t0(), - selectedRegion.t1()); - if (dest) - FinishCopy(n, std::move(dest), newClipboard); - } - ); - - // Survived possibility of exceptions. Commit changes to the clipboard now. - newClipboard.Swap(*AudacityProject::msClipboard); - - AudacityProject::msClipT0 = selectedRegion.t0(); - AudacityProject::msClipT1 = selectedRegion.t1(); - AudacityProject::msClipProject = &project; - - project.PushState(_("Split-cut to the clipboard"), _("Split Cut")); - - project.RedrawProject(); - - if (historyWindow) - historyWindow->UpdateDisplay(); -} - - -void MenuCommandHandler::OnCopy(const CommandContext &context) -{ - auto &project = context.project; - auto tracks = project.GetTracks(); - auto trackPanel = project.GetTrackPanel(); - auto &selectedRegion = project.GetViewInfo().selectedRegion; - auto historyWindow = project.GetHistoryWindow(); - - for (auto lt : tracks->Selected< LabelTrack >()) { - if (lt->CopySelectedText()) { - //trackPanel->Refresh(false); - return; - } - } - - AudacityProject::ClearClipboard(); - - auto pNewClipboard = TrackList::Create(); - auto &newClipboard = *pNewClipboard; - - for (auto n : tracks->Selected()) { - auto dest = n->Copy(selectedRegion.t0(), - selectedRegion.t1()); - FinishCopy(n, std::move(dest), newClipboard); - } - - // Survived possibility of exceptions. Commit changes to the clipboard now. - newClipboard.Swap(*AudacityProject::msClipboard); - - AudacityProject::msClipT0 = selectedRegion.t0(); - AudacityProject::msClipT1 = selectedRegion.t1(); - AudacityProject::msClipProject = &project; - - //Make sure the menus/toolbar states get updated - trackPanel->Refresh(false); - - if (historyWindow) - historyWindow->UpdateDisplay(); -} - -void MenuCommandHandler::OnPaste(const CommandContext &context) -{ - auto &project = context.project; - auto tracks = project.GetTracks(); - auto trackPanel = project.GetTrackPanel(); - auto trackFactory = project.GetTrackFactory(); - auto &selectedRegion = project.GetViewInfo().selectedRegion; - auto isSyncLocked = project.IsSyncLocked(); - - // Handle text paste (into active label) first. - if (this->HandlePasteText(project)) - return; - - // If nothing's selected, we just insert NEW tracks. - if (this->HandlePasteNothingSelected(project)) - return; - - auto clipTrackRange = AudacityProject::msClipboard->Any< const Track >(); - if (clipTrackRange.empty()) - return; - - // Otherwise, paste into the selected tracks. - double t0 = selectedRegion.t0(); - double t1 = selectedRegion.t1(); - - auto pN = tracks->Any().begin(); - - Track *ff = NULL; - const Track *lastClipBeforeMismatch = NULL; - const Track *mismatchedClip = NULL; - const Track *prevClip = NULL; - - bool bAdvanceClipboard = true; - bool bPastedSomething = false; - - auto pC = clipTrackRange.begin(); - size_t nnChannels=0, ncChannels=0; - while (*pN && *pC) { - auto n = *pN; - auto c = *pC; - if (n->GetSelected()) { - bAdvanceClipboard = true; - if (mismatchedClip) - c = mismatchedClip; - if (!c->SameKindAs(*n)) { - if (!mismatchedClip) { - lastClipBeforeMismatch = prevClip; - mismatchedClip = c; - } - bAdvanceClipboard = false; - c = lastClipBeforeMismatch; - - - // If the types still don't match... - while (c && !c->SameKindAs(*n)) { - prevClip = c; - c = * ++ pC; - } - } - - // Handle case where the first track in clipboard - // is of different type than the first selected track - if (!c) { - c = mismatchedClip; - while (n && (!c->SameKindAs(*n) || !n->GetSelected())) - { - // Must perform sync-lock adjustment before incrementing n - if (n->IsSyncLockSelected()) { - auto newT1 = t0 + - (AudacityProject::msClipT1 - AudacityProject::msClipT0); - if (t1 != newT1 && t1 <= n->GetEndTime()) { - n->SyncLockAdjust(t1, newT1); - bPastedSomething = true; - } - } - n = * ++ pN; - } - if (!n) - c = NULL; - } - - // The last possible case for cross-type pastes: triggered when we try to - // paste 1+ tracks from one type into 1+ tracks of another type. If - // there's a mix of types, this shouldn't run. - if (!c) - // Throw, so that any previous changes to the project in this loop - // are discarded. - throw SimpleMessageBoxException{ - _("Pasting one type of track into another is not allowed.") - }; - - // We should need this check only each time we visit the leading - // channel - if ( n->IsLeader() ) { - wxASSERT( c->IsLeader() ); // the iteration logic should ensure this - - auto cChannels = TrackList::Channels(c); - ncChannels = cChannels.size(); - auto nChannels = TrackList::Channels(n); - nnChannels = nChannels.size(); - - // When trying to copy from stereo to mono track, show error and exit - // TODO: Automatically offer user to mix down to mono (unfortunately - // this is not easy to implement - if (ncChannels > nnChannels) - { - if (ncChannels > 2) { - // TODO: more-than-two-channels-message - // Re-word the error message - } - // else - - // Throw, so that any previous changes to the project in this loop - // are discarded. - throw SimpleMessageBoxException{ - _("Copying stereo audio into a mono track is not allowed.") - }; - } - } - - if (!ff) - ff = n; - - wxASSERT( n && c && n->SameKindAs(*c) ); - Maybe locker; - - n->TypeSwitch( - [&](WaveTrack *wn){ - const auto wc = static_cast(c); - if (AudacityProject::msClipProject != &project) - // Cause duplication of block files on disk, when copy is - // between projects - locker.create(wc); - bPastedSomething = true; - wn->ClearAndPaste(t0, t1, wc, true, true); - }, - [&](LabelTrack *ln){ - // Per Bug 293, users expect labels to move on a paste into - // a label track. - ln->Clear(t0, t1); - - ln->ShiftLabelsOnInsert( - AudacityProject::msClipT1 - AudacityProject::msClipT0, t0); - - bPastedSomething |= ln->PasteOver(t0, c); - }, - [&](Track *){ - bPastedSomething = true; - n->Clear(t0, t1); - n->Paste(t0, c); - } - ); - - --nnChannels; - --ncChannels; - - // When copying from mono to stereo track, paste the wave form - // to both channels - // TODO: more-than-two-channels - // This will replicate the last pasted channel as many times as needed - while (nnChannels > 0 && ncChannels == 0) - { - n = * ++ pN; - --nnChannels; - - n->TypeSwitch( - [&](WaveTrack *wn){ - bPastedSomething = true; - // Note: rely on locker being still be in scope! - wn->ClearAndPaste(t0, t1, c, true, true); - }, - [&](Track *){ - n->Clear(t0, t1); - bPastedSomething = true; - n->Paste(t0, c); - } - ); - } - - if (bAdvanceClipboard) { - prevClip = c; - c = * ++ pC; - } - } // if (n->GetSelected()) - else if (n->IsSyncLockSelected()) - { - auto newT1 = t0 + - (AudacityProject::msClipT1 - AudacityProject::msClipT0); - if (t1 != newT1 && t1 <= n->GetEndTime()) { - n->SyncLockAdjust(t1, newT1); - bPastedSomething = true; - } - } - ++pN; - } - - // This block handles the cases where our clipboard is smaller - // than the amount of selected destination tracks. We take the - // last wave track, and paste that one into the remaining - // selected tracks. - if ( *pN && ! *pC ) - { - const auto wc = - *AudacityProject::msClipboard->Any< const WaveTrack >().rbegin(); - Maybe locker; - if (AudacityProject::msClipProject != &project && wc) - // Cause duplication of block files on disk, when copy is - // between projects - locker.create(static_cast(wc)); - - tracks->Any().StartingWith(*pN).Visit( - [&](WaveTrack *wt, const Track::Fallthrough &fallthrough) { - if (!wt->GetSelected()) - return fallthrough(); - - if (wc) { - bPastedSomething = true; - wt->ClearAndPaste(t0, t1, wc, true, true); - } - else { - auto tmp = trackFactory->NewWaveTrack( - wt->GetSampleFormat(), wt->GetRate()); - tmp->InsertSilence(0.0, - AudacityProject::msClipT1 - AudacityProject::msClipT0); // MJS: Is this correct? - tmp->Flush(); - - bPastedSomething = true; - wt->ClearAndPaste(t0, t1, tmp.get(), true, true); - } - }, - [&](LabelTrack *lt, const Track::Fallthrough &fallthrough) { - if (!lt->GetSelected()) - return fallthrough(); - - lt->Clear(t0, t1); - - // As above, only shift labels if sync-lock is on. - if (isSyncLocked) - lt->ShiftLabelsOnInsert( - AudacityProject::msClipT1 - AudacityProject::msClipT0, t0); - }, - [&](Track *n) { - if (n->IsSyncLockSelected()) - n->SyncLockAdjust(t1, t0 + - AudacityProject::msClipT1 - AudacityProject::msClipT0); - } - ); - } - - // TODO: What if we clicked past the end of the track? - - if (bPastedSomething) - { - selectedRegion.setT1( - t0 + AudacityProject::msClipT1 - AudacityProject::msClipT0); - - project.PushState(_("Pasted from the clipboard"), _("Paste")); - - project.RedrawProject(); - - if (ff) - trackPanel->EnsureVisible(ff); - } -} - -// Handle text paste (into active label), if any. Return true if did paste. -// (This was formerly the first part of overly-long OnPaste.) -bool MenuCommandHandler::HandlePasteText(AudacityProject &project) -{ - auto tracks = project.GetTracks(); - auto trackPanel = project.GetTrackPanel(); - auto &selectedRegion = project.GetViewInfo().selectedRegion; - - for (auto pLabelTrack : tracks->Any()) - { - // Does this track have an active label? - if (pLabelTrack->IsSelected()) { - - // Yes, so try pasting into it - if (pLabelTrack->PasteSelectedText(selectedRegion.t0(), - selectedRegion.t1())) - { - project.PushState(_("Pasted text from the clipboard"), _("Paste")); - - // Make sure caret is in view - int x; - if (pLabelTrack->CalcCursorX(&x)) { - trackPanel->ScrollIntoView(x); - } - - // Redraw everyting (is that necessary???) and bail - project.RedrawProject(); - return true; - } - } - } - return false; -} - -// Return true if nothing selected, regardless of paste result. -// If nothing was selected, create and paste into NEW tracks. -// (This was formerly the second part of overly-long OnPaste.) -bool MenuCommandHandler::HandlePasteNothingSelected(AudacityProject &project) -{ - auto tracks = project.GetTracks(); - auto trackFactory = project.GetTrackFactory(); - auto trackPanel = project.GetTrackPanel(); - auto &selectedRegion = project.GetViewInfo().selectedRegion; - - // First check whether anything's selected. - if (tracks->Selected()) - return false; - else - { - auto clipTrackRange = AudacityProject::msClipboard->Any< const Track >(); - if (clipTrackRange.empty()) - return true; // nothing to paste - - Track* pFirstNewTrack = NULL; - for (auto pClip : clipTrackRange) { - Maybe locker; - - Track::Holder uNewTrack; - Track *pNewTrack; - pClip->TypeSwitch( - [&](const WaveTrack *wc) { - if ((AudacityProject::msClipProject != &project)) - // Cause duplication of block files on disk, when copy is - // between projects - locker.create(wc); - uNewTrack = trackFactory->NewWaveTrack( - wc->GetSampleFormat(), wc->GetRate()), - pNewTrack = uNewTrack.get(); - }, -#ifdef USE_MIDI - [&](const NoteTrack *) { - uNewTrack = trackFactory->NewNoteTrack(), - pNewTrack = uNewTrack.get(); - }, -#endif - [&](const LabelTrack *) { - uNewTrack = trackFactory->NewLabelTrack(), - pNewTrack = uNewTrack.get(); - }, - [&](const TimeTrack *) { - // Maintain uniqueness of the time track! - pNewTrack = tracks->GetTimeTrack(); - if (!pNewTrack) - uNewTrack = trackFactory->NewTimeTrack(), - pNewTrack = uNewTrack.get(); - } - ); - - wxASSERT(pClip); - - pNewTrack->Paste(0.0, pClip); - - if (!pFirstNewTrack) - pFirstNewTrack = pNewTrack; - - pNewTrack->SetSelected(true); - if (uNewTrack) - FinishCopy(pClip, std::move(uNewTrack), *tracks); - else - Track::FinishCopy(pClip, pNewTrack); - } - - // Select some pasted samples, which is probably impossible to get right - // with various project and track sample rates. - // So do it at the sample rate of the project - AudacityProject *p = GetActiveProject(); - double projRate = p->GetRate(); - double quantT0 = QUANTIZED_TIME(AudacityProject::msClipT0, projRate); - double quantT1 = QUANTIZED_TIME(AudacityProject::msClipT1, projRate); - selectedRegion.setTimes( - 0.0, // anywhere else and this should be - // half a sample earlier - quantT1 - quantT0); - - project.PushState(_("Pasted from the clipboard"), _("Paste")); - - project.RedrawProject(); - - if (pFirstNewTrack) - trackPanel->EnsureVisible(pFirstNewTrack); - - return true; - } -} - - -// Creates a NEW label in each selected label track with text from the system -// clipboard -void MenuCommandHandler::OnPasteNewLabel(const CommandContext &context) -{ - auto &project = context.project; - auto tracks = project.GetTracks(); - auto trackFactory = project.GetTrackFactory(); - auto trackPanel = project.GetTrackPanel(); - auto &selectedRegion = project.GetViewInfo().selectedRegion; - - bool bPastedSomething = false; - - { - auto trackRange = tracks->Selected< const LabelTrack >(); - if (trackRange.empty()) - { - // If there are no selected label tracks, try to choose the first label - // track after some other selected track - Track *t = *tracks->Selected().begin() - .Filter( &Track::Any ) - .Filter(); - - // If no match found, add one - if (!t) { - t = tracks->Add(trackFactory->NewLabelTrack()); - } - - // Select this track so the loop picks it up - t->SetSelected(true); - } - } - - LabelTrack *plt = NULL; // the previous track - for ( auto lt : tracks->Selected< LabelTrack >() ) - { - // Unselect the last label, so we'll have just one active label when - // we're done - if (plt) - plt->Unselect(); - - // Add a NEW label, paste into it - // Paul L: copy whatever defines the selected region, not just times - lt->AddLabel(selectedRegion); - if (lt->PasteSelectedText(selectedRegion.t0(), - selectedRegion.t1())) - bPastedSomething = true; - - // Set previous track - plt = lt; - } - - // plt should point to the last label track pasted to -- ensure it's visible - // and set focus - if (plt) { - trackPanel->EnsureVisible(plt); - trackPanel->SetFocus(); - } - - if (bPastedSomething) { - project.PushState(_("Pasted from the clipboard"), _("Paste Text to New Label")); - - // Is this necessary? (carried over from former logic in OnPaste()) - project.RedrawProject(); - } -} - -void MenuCommandHandler::OnPasteOver(const CommandContext &context) // not currently in use it appears -{ - auto &project = context.project; - auto &selectedRegion = project.GetViewInfo().selectedRegion; - - if((AudacityProject::msClipT1 - AudacityProject::msClipT0) > 0.0) - { - selectedRegion.setT1( - selectedRegion.t0() + - (AudacityProject::msClipT1 - AudacityProject::msClipT0)); - // MJS: pointless, given what we do in OnPaste? - } - OnPaste(context); - - return; -} - -void MenuCommandHandler::OnTrim(const CommandContext &context) -{ - auto &project = context.project; - auto tracks = project.GetTracks(); - auto &selectedRegion = project.GetViewInfo().selectedRegion; - - if (selectedRegion.isPoint()) - return; - - tracks->Selected().Visit( -#ifdef USE_MIDI - [&](NoteTrack *nt) { - nt->Trim(selectedRegion.t0(), - selectedRegion.t1()); - }, -#endif - [&](WaveTrack *wt) { - //Delete the section before the left selector - wt->Trim(selectedRegion.t0(), - selectedRegion.t1()); - } - ); - - project.PushState( - wxString::Format( - _("Trim selected audio tracks from %.2f seconds to %.2f seconds"), - selectedRegion.t0(), selectedRegion.t1()), - _("Trim Audio")); - - project.RedrawProject(); -} - -void MenuCommandHandler::OnDelete(const CommandContext &context) -{ - auto &project = context.project; - project.Clear(); -} - -void MenuCommandHandler::OnSplitDelete(const CommandContext &context) -{ - auto &project = context.project; - auto tracks = project.GetTracks(); - auto &selectedRegion = project.GetViewInfo().selectedRegion; - - tracks->Selected().Visit( - [&](WaveTrack *wt) { - wt->SplitDelete(selectedRegion.t0(), - selectedRegion.t1()); - }, - [&](Track *n) { - n->Silence(selectedRegion.t0(), - selectedRegion.t1()); - } - ); - - project.PushState( - wxString::Format(_("Split-deleted %.2f seconds at t=%.2f"), - selectedRegion.duration(), - selectedRegion.t0()), - _("Split Delete")); - - project.RedrawProject(); -} - -void MenuCommandHandler::OnDisjoin(const CommandContext &context) -{ - auto &project = context.project; - auto tracks = project.GetTracks(); - auto &selectedRegion = project.GetViewInfo().selectedRegion; - - for (auto wt : tracks->Selected< WaveTrack >()) - wt->Disjoin(selectedRegion.t0(), - selectedRegion.t1()); - - project.PushState( - wxString::Format(_("Detached %.2f seconds at t=%.2f"), - selectedRegion.duration(), - selectedRegion.t0()), - _("Detach")); - - project.RedrawProject(); -} - -void MenuCommandHandler::OnJoin(const CommandContext &context) -{ - auto &project = context.project; - auto tracks = project.GetTracks(); - auto &selectedRegion = project.GetViewInfo().selectedRegion; - - for (auto wt : tracks->Selected< WaveTrack >()) - wt->Join(selectedRegion.t0(), - selectedRegion.t1()); - - project.PushState( - wxString::Format(_("Joined %.2f seconds at t=%.2f"), - selectedRegion.duration(), - selectedRegion.t0()), - _("Join")); - - project.RedrawProject(); -} - -void MenuCommandHandler::OnSilence(const CommandContext &context) -{ - auto &project = context.project; - auto tracks = project.GetTracks(); - auto trackPanel = project.GetTrackPanel(); - auto &selectedRegion = project.GetViewInfo().selectedRegion; - - for ( auto n : tracks->Selected< AudioTrack >() ) - n->Silence(selectedRegion.t0(), selectedRegion.t1()); - - project.PushState( - wxString::Format(_("Silenced selected tracks for %.2f seconds at %.2f"), - selectedRegion.duration(), - selectedRegion.t0()), - _("Silence")); - - trackPanel->Refresh(false); -} - -void MenuCommandHandler::OnDuplicate(const CommandContext &context) -{ - auto &project = context.project; - auto tracks = project.GetTracks(); - auto &selectedRegion = project.GetViewInfo().selectedRegion; - - // This iteration is unusual because we add to the list inside the loop - auto range = tracks->Selected(); - auto last = *range.rbegin(); - for (auto n : range) { - // Make copies not for clipboard but for direct addition to the project - auto dest = n->Copy(selectedRegion.t0(), - selectedRegion.t1(), false); - dest->Init(*n); - dest->SetOffset(wxMax(selectedRegion.t0(), n->GetOffset())); - tracks->Add(std::move(dest)); - - // This break is really needed, else we loop infinitely - if (n == last) - break; - } - - project.PushState(_("Duplicated"), _("Duplicate")); - - project.RedrawProject(); -} - -void MenuCommandHandler::OnCutLabels(const CommandContext &context) -{ - auto &project = context.project; - auto &selectedRegion = project.GetViewInfo().selectedRegion; - - if( selectedRegion.isPoint() ) - return; - - // Because of grouping the copy may need to operate on different tracks than - // the clear, so we do these actions separately. - project.EditClipboardByLabel( &WaveTrack::CopyNonconst ); - - if( gPrefs->Read( wxT( "/GUI/EnableCutLines" ), ( long )0 ) ) - project.EditByLabel( &WaveTrack::ClearAndAddCutLine, true ); - else - project.EditByLabel( &WaveTrack::Clear, true ); - - AudacityProject::msClipProject = &project; - - selectedRegion.collapseToT0(); - - project.PushState( - /* i18n-hint: (verb) past tense. Audacity has just cut the labeled audio regions.*/ - _( "Cut labeled audio regions to clipboard" ), - /* i18n-hint: (verb)*/ - _( "Cut Labeled Audio" ) ); - - project.RedrawProject(); -} - -void MenuCommandHandler::OnSplitCutLabels(const CommandContext &context) -{ - auto &project = context.project; - auto &selectedRegion = project.GetViewInfo().selectedRegion; - - if( selectedRegion.isPoint() ) - return; - - project.EditClipboardByLabel( &WaveTrack::SplitCut ); - - AudacityProject::msClipProject = &project; - - project.PushState( - /* i18n-hint: (verb) Audacity has just split cut the labeled audio regions*/ - _( "Split Cut labeled audio regions to clipboard" ), - /* i18n-hint: (verb) Do a special kind of cut on the labels*/ - _( "Split Cut Labeled Audio" ) ); - - project.RedrawProject(); -} - -void MenuCommandHandler::OnCopyLabels(const CommandContext &context) -{ - auto &project = context.project; - auto trackPanel = project.GetTrackPanel(); - auto &selectedRegion = project.GetViewInfo().selectedRegion; - - if( selectedRegion.isPoint() ) - return; - - project.EditClipboardByLabel( &WaveTrack::CopyNonconst ); - - AudacityProject::msClipProject = &project; - - project.PushState( _( "Copied labeled audio regions to clipboard" ), - /* i18n-hint: (verb)*/ - _( "Copy Labeled Audio" ) ); - - trackPanel->Refresh( false ); -} - -void MenuCommandHandler::OnDeleteLabels(const CommandContext &context) -{ - auto &project = context.project; - auto &selectedRegion = project.GetViewInfo().selectedRegion; - - if( selectedRegion.isPoint() ) - return; - - project.EditByLabel( &WaveTrack::Clear, true ); - - selectedRegion.collapseToT0(); - - project.PushState( - /* i18n-hint: (verb) Audacity has just deleted the labeled audio regions*/ - _( "Deleted labeled audio regions" ), - /* i18n-hint: (verb)*/ - _( "Delete Labeled Audio" ) ); - - project.RedrawProject(); -} - -void MenuCommandHandler::OnSplitDeleteLabels(const CommandContext &context) -{ - auto &project = context.project; - auto &selectedRegion = project.GetViewInfo().selectedRegion; - - if( selectedRegion.isPoint() ) - return; - - project.EditByLabel( &WaveTrack::SplitDelete, false ); - - project.PushState( - /* i18n-hint: (verb) Audacity has just done a special kind of DELETE on the labeled audio regions */ - _( "Split Deleted labeled audio regions" ), - /* i18n-hint: (verb) Do a special kind of DELETE on labeled audio regions*/ - _( "Split Delete Labeled Audio" ) ); - - project.RedrawProject(); -} - -void MenuCommandHandler::OnSilenceLabels(const CommandContext &context) -{ - auto &project = context.project; - auto trackPanel = project.GetTrackPanel(); - auto &selectedRegion = project.GetViewInfo().selectedRegion; - - if( selectedRegion.isPoint() ) - return; - - project.EditByLabel( &WaveTrack::Silence, false ); - - project.PushState( - /* i18n-hint: (verb)*/ - _( "Silenced labeled audio regions" ), - /* i18n-hint: (verb)*/ - _( "Silence Labeled Audio" ) ); - - trackPanel->Refresh( false ); -} - -void MenuCommandHandler::OnSplitLabels(const CommandContext &context) -{ - auto &project = context.project; - - project.EditByLabel( &WaveTrack::Split, false ); - - project.PushState( - /* i18n-hint: (verb) past tense. Audacity has just split the labeled audio (a point or a region)*/ - _( "Split labeled audio (points or regions)" ), - /* i18n-hint: (verb)*/ - _( "Split Labeled Audio" ) ); - - project.RedrawProject(); -} - -void MenuCommandHandler::OnJoinLabels(const CommandContext &context) -{ - auto &project = context.project; - auto &selectedRegion = project.GetViewInfo().selectedRegion; - - if( selectedRegion.isPoint() ) - return; - - project.EditByLabel( &WaveTrack::Join, false ); - - project.PushState( - /* i18n-hint: (verb) Audacity has just joined the labeled audio (points or regions)*/ - _( "Joined labeled audio (points or regions)" ), - /* i18n-hint: (verb)*/ - _( "Join Labeled Audio" ) ); - - project.RedrawProject(); -} - -void MenuCommandHandler::OnDisjoinLabels(const CommandContext &context) -{ - auto &project = context.project; - auto &selectedRegion = project.GetViewInfo().selectedRegion; - - if( selectedRegion.isPoint() ) - return; - - project.EditByLabel( &WaveTrack::Disjoin, false ); - - project.PushState( - /* i18n-hint: (verb) Audacity has just detached the labeled audio regions. - This message appears in history and tells you about something - Audacity has done.*/ - _( "Detached labeled audio regions" ), - /* i18n-hint: (verb)*/ - _( "Detach Labeled Audio" ) ); - - project.RedrawProject(); -} - -void MenuCommandHandler::OnSplit(const CommandContext &context) -{ - auto &project = context.project; - auto tracks = project.GetTracks(); - auto trackPanel = project.GetTrackPanel(); - auto &selectedRegion = project.GetViewInfo().selectedRegion; - - double sel0 = selectedRegion.t0(); - double sel1 = selectedRegion.t1(); - - for (auto wt : tracks->Selected< WaveTrack >()) - wt->Split( sel0, sel1 ); - - project.PushState(_("Split"), _("Split")); - trackPanel->Refresh(false); -#if 0 -//ANSWER-ME: Do we need to keep this commented out OnSplit() code? -// This whole section no longer used... - /* - * Previous (pre-multiclip) implementation of "Split" command - * This does work only when a range is selected! - * - TrackListIterator iter(tracks); - - Track *n = iter.First(); - Track *dest; - - TrackList newTracks; - - while (n) { - if (n->GetSelected()) { - double sel0 = selectedRegion.t0(); - double sel1 = selectedRegion.t1(); - - dest = n->Copy(sel0, sel1); - dest->Init(*n); - dest->SetOffset(wxMax(sel0, n->GetOffset())); - - if (sel1 >= n->GetEndTime()) - n->Clear(sel0, sel1); - else if (sel0 <= n->GetOffset()) { - n->Clear(sel0, sel1); - n->SetOffset(sel1); - } else - n->Silence(sel0, sel1); - - newTracks.Add(dest); - } - n = iter.Next(); - } - - TrackListIterator nIter(&newTracks); - n = nIter.First(); - while (n) { - tracks->Add(n); - n = nIter.Next(); - } - - PushState(_("Split"), _("Split")); - - FixScrollbars(); - trackPanel->Refresh(false); - */ -#endif -} - -void MenuCommandHandler::OnSplitNew(const CommandContext &context) -{ - auto &project = context.project; - auto tracks = project.GetTracks(); - auto &selectedRegion = project.GetViewInfo().selectedRegion; - - Track::Holder dest; - - // This iteration is unusual because we add to the list inside the loop - auto range = tracks->Selected(); - auto last = *range.rbegin(); - for (auto track : range) { - track->TypeSwitch( - [&](WaveTrack *wt) { - // Clips must be aligned to sample positions or the NEW clip will not fit in the gap where it came from - double offset = wt->GetOffset(); - offset = wt->LongSamplesToTime(wt->TimeToLongSamples(offset)); - double newt0 = wt->LongSamplesToTime(wt->TimeToLongSamples( - selectedRegion.t0())); - double newt1 = wt->LongSamplesToTime(wt->TimeToLongSamples( - selectedRegion.t1())); - dest = wt->SplitCut(newt0, newt1); - if (dest) { - dest->SetOffset(wxMax(newt0, offset)); - FinishCopy(wt, std::move(dest), *tracks); - } - } -#if 0 - , - // LL: For now, just skip all non-wave tracks since the other do not - // yet support proper splitting. - [&](Track *n) { - dest = n->Cut(viewInfo.selectedRegion.t0(), - viewInfo.selectedRegion.t1()); - if (dest) { - dest->SetOffset(wxMax(0, n->GetOffset())); - FinishCopy(n, std::move(dest), *tracks); - } - } -#endif - ); - if (track == last) - break; - } - - project.PushState(_("Split to new track"), _("Split New")); - - project.RedrawProject(); -} int MenuCommandHandler::CountSelectedTracks(TrackList &tracks) { @@ -7707,35 +6223,6 @@ void MenuCommandHandler::OnShowEffectsRack(const &WXUNUSED(context) ) // Project Menu // -void MenuCommandHandler::OnEditMetadata(const CommandContext &context) -{ - auto &project = context.project; - (void)DoEditMetadata( project, - _("Edit Metadata Tags"), _("Metadata Tags"), true); -} - -bool MenuCommandHandler::DoEditMetadata -(AudacityProject &project, - const wxString &title, const wxString &shortUndoDescription, bool force) -{ - auto tags = project.GetTags(); - - // Back up my tags - auto newTags = tags->Duplicate(); - - if (newTags->ShowEditDialog(&project, title, force)) { - if (*tags != *newTags) { - // Commit the change to project state only now. - project.SetTags( newTags ); - project.PushState(title, shortUndoDescription); - } - - return true; - } - - return false; -} - void MenuCommandHandler::HandleMixAndRender (AudacityProject &project, bool toNewTrack) { @@ -8987,73 +7474,6 @@ void MenuCommandHandler::OnPunchAndRoll(const CommandContext &context) } #endif -int MenuCommandHandler::DoAddLabel( - AudacityProject &project, const SelectedRegion ®ion, bool preserveFocus) -{ - auto tracks = project.GetTracks(); - auto trackPanel = project.GetTrackPanel(); - auto trackFactory = project.GetTrackFactory(); - - wxString title; // of label - - bool useDialog; - gPrefs->Read(wxT("/GUI/DialogForNameNewLabel"), &useDialog, false); - if (useDialog) { - if (LabelTrack::DialogForLabelName( - project, region, wxEmptyString, title) == wxID_CANCEL) - return -1; // index - } - - // If the focused track is a label track, use that - Track *const pFocusedTrack = trackPanel->GetFocusedTrack(); - - // Look for a label track at or after the focused track - auto iter = pFocusedTrack - ? tracks->Find(pFocusedTrack) - : tracks->Any().begin(); - auto lt = * iter.Filter< LabelTrack >(); - - // If none found, start a NEW label track and use it - if (!lt) { - lt = static_cast - (tracks->Add(trackFactory->NewLabelTrack())); - } - -// LLL: Commented as it seemed a little forceful to remove users -// selection when adding the label. This does not happen if -// you select several tracks and the last one of those is a -// label track...typing a label will not clear the selections. -// -// SelectNone(); - lt->SetSelected(true); - - int focusTrackNumber; - if (useDialog) { - focusTrackNumber = -2; - } - else { - focusTrackNumber = -1; - if (pFocusedTrack && preserveFocus) { - // Must remember the track to re-focus after finishing a label edit. - // do NOT identify it by a pointer, which might dangle! Identify - // by position. - focusTrackNumber = pFocusedTrack->GetIndex(); - } - } - - int index = lt->AddLabel(region, title, focusTrackNumber); - - project.PushState(_("Added label"), _("Label")); - - project.RedrawProject(); - if (!useDialog) { - trackPanel->EnsureVisible(lt); - } - trackPanel->SetFocus(); - - return index; -} - void MenuCommandHandler::OnMoveSelectionWithTracks(const CommandContext &WXUNUSED(context) ) { bool bMoveWith; @@ -9081,41 +7501,6 @@ void MenuCommandHandler::OnSyncLock(const CommandContext &context) -void MenuCommandHandler::OnAddLabel(const CommandContext &context) -{ - auto &project = context.project; - auto &selectedRegion = project.GetViewInfo().selectedRegion; - - DoAddLabel(project, selectedRegion); -} - -void MenuCommandHandler::OnAddLabelPlaying(const CommandContext &context) -{ - auto &project = context.project; - auto token = project.GetAudioIOToken(); - - if (token > 0 && - gAudioIO->IsStreamActive(token)) { - double indicator = gAudioIO->GetStreamTime(); - DoAddLabel(project, SelectedRegion(indicator, indicator), true); - } -} - -void MenuCommandHandler::OnEditLabels(const CommandContext &context) -{ - auto &project = context.project; - LabelTrack::DoEditLabels(project); -} - -void MenuCommandHandler::OnToggleTypeToCreateLabel(const CommandContext &WXUNUSED(context) ) -{ - bool typeToCreateLabel; - gPrefs->Read(wxT("/GUI/TypeToCreateLabel"), &typeToCreateLabel, true); - gPrefs->Write(wxT("/GUI/TypeToCreateLabel"), !typeToCreateLabel); - gPrefs->Flush(); - MenuManager::ModifyAllProjectToolbarMenus(); -} - void MenuCommandHandler::OnRemoveTracks(const CommandContext &context) { auto &project = context.project; diff --git a/src/Menus.h b/src/Menus.h index f36340899..4e569bafc 100644 --- a/src/Menus.h +++ b/src/Menus.h @@ -49,11 +49,6 @@ struct MenuCommandHandler final MenuCommandHandler(); ~MenuCommandHandler(); -//Adds label and returns index of label in labeltrack. -int DoAddLabel( - AudacityProject &project, - const SelectedRegion& region, bool preserveFocus = false); - double NearestZeroCrossing(AudacityProject &project, double t0); // Selecting a tool from the keyboard @@ -220,50 +215,8 @@ void OnMacBringAllToFront(const CommandContext &context ); void OnCheckDependencies(const CommandContext &context ); -void OnPreferences(const CommandContext &context ); -void OnReloadPreferences(const CommandContext &context ); - // Edit Menu -void OnUndo(const CommandContext &context ); -void OnRedo(const CommandContext &context ); - -static void FinishCopy( - const Track *n, std::unique_ptr &&dest, TrackList &list); - -void OnCut(const CommandContext &context ); -void OnSplitCut(const CommandContext &context ); -void OnCopy(const CommandContext &context ); - -void OnPaste(const CommandContext &context ); - -bool HandlePasteText(AudacityProject &project); // Handle text paste (into active label), if any. Return true if pasted. -bool HandlePasteNothingSelected(AudacityProject &project); // Return true if nothing selected, regardless of paste result. - - -void OnPasteNewLabel(const CommandContext &context ); -void OnPasteOver(const CommandContext &context ); -void OnTrim(const CommandContext &context ); - -void OnDelete(const CommandContext &context ); -void OnSplitDelete(const CommandContext &context ); -void OnSilence(const CommandContext &context ); - -void OnSplit(const CommandContext &context ); -void OnSplitNew(const CommandContext &context ); -void OnJoin(const CommandContext &context ); -void OnDisjoin(const CommandContext &context ); -void OnDuplicate(const CommandContext &context ); - -void OnCutLabels(const CommandContext &context ); -void OnSplitCutLabels(const CommandContext &context ); -void OnCopyLabels(const CommandContext &context ); -void OnDeleteLabels(const CommandContext &context ); -void OnSplitDeleteLabels(const CommandContext &context ); -void OnSilenceLabels(const CommandContext &context ); -void OnSplitLabels(const CommandContext &context ); -void OnJoinLabels(const CommandContext &context ); -void OnDisjoinLabels(const CommandContext &context ); void OnSelectTimeAndTracks (AudacityProject &project, bool bAllTime, bool bAllTracks); @@ -391,10 +344,6 @@ void OnPunchAndRoll(const CommandContext &context); // Import Submenu -void OnEditMetadata(const CommandContext &context ); -bool DoEditMetadata(AudacityProject &project, - const wxString &title, const wxString &shortUndoDescription, bool force); - void OnMixAndRender(const CommandContext &context ); void OnMixAndRenderToNewTrack(const CommandContext &context ); void HandleMixAndRender(AudacityProject &project, bool toNewTrack); @@ -451,10 +400,6 @@ void OnTimerRecord(const CommandContext &context ); void OnRemoveTracks(const CommandContext &context ); void OnMoveSelectionWithTracks(const CommandContext &context ); void OnSyncLock(const CommandContext &context ); -void OnAddLabel(const CommandContext &context ); -void OnAddLabelPlaying(const CommandContext &context ); -void OnEditLabels(const CommandContext &context ); -void OnToggleTypeToCreateLabel(const CommandContext &context ); // Effect Menu @@ -626,6 +571,14 @@ AudacityProject *DoImportMIDI( AudacityProject *pProject, const wxString &fileName ); } +namespace EditActions { +bool DoEditMetadata( + AudacityProject &project, + const wxString &title, const wxString &shortUndoDescription, bool force ); +void DoReloadPreferences( AudacityProject & ); +void DoUndo( AudacityProject &project ); +} + #endif diff --git a/src/Project.cpp b/src/Project.cpp index 20ab7c85d..f9b9c663c 100644 --- a/src/Project.cpp +++ b/src/Project.cpp @@ -5575,7 +5575,7 @@ You are saving directly to a slow external storage device\n\ // Reset timer record if (IsTimerRecordCancelled()) { - GetMenuCommandHandler(*this).OnUndo(*this); + EditActions::DoUndo( *this ); ResetTimerRecordCancelled(); } diff --git a/src/commands/PreferenceCommands.cpp b/src/commands/PreferenceCommands.cpp index 359f35682..ebc6749eb 100644 --- a/src/commands/PreferenceCommands.cpp +++ b/src/commands/PreferenceCommands.cpp @@ -21,7 +21,6 @@ SetPreferenceCommand classes #include "../Prefs.h" #include "../ShuttleGui.h" #include "../commands/CommandContext.h" -#include "../Project.h" // for "OnReloadPreferences". bool GetPreferenceCommand::DefineParams( ShuttleParams & S ){ S.Define( mName, wxT("Name"), wxT("") ); @@ -75,7 +74,7 @@ bool SetPreferenceCommand::Apply(const CommandContext & context) bool bOK = gPrefs->Write(mName, mValue) && gPrefs->Flush(); if( bOK && mbReload ){ auto &project = context.project; - GetMenuCommandHandler(project).OnReloadPreferences( context ); + EditActions::DoReloadPreferences( project ); } return bOK; } diff --git a/src/export/Export.cpp b/src/export/Export.cpp index 9f89c09d8..675257059 100644 --- a/src/export/Export.cpp +++ b/src/export/Export.cpp @@ -30,6 +30,7 @@ #include "../Audacity.h" #include "Export.h" +#include "Menus.h" #include #include @@ -59,7 +60,6 @@ #include "../DirManager.h" #include "../FileFormats.h" #include "../Internat.h" -#include "../Menus.h" #include "../Mix.h" #include "../Prefs.h" #include "../Project.h" @@ -391,7 +391,7 @@ bool Exporter::Process(AudacityProject *project, bool selectedOnly, double t0, d // Let user edit MetaData if (mPlugins[mFormat]->GetCanMetaData(mSubFormat)) { - if (!(GetMenuCommandHandler(*project).DoEditMetadata( *project, + if (!(EditActions::DoEditMetadata( *project, _("Edit Metadata Tags"), _("Exported Tags"), mProject->GetShowId3Dialog()))) { return false; @@ -1029,7 +1029,7 @@ bool Exporter::SetAutoExportOptions(AudacityProject *project) { // Let user edit MetaData if (mPlugins[mFormat]->GetCanMetaData(mSubFormat)) { - if (!(GetMenuCommandHandler(*project).DoEditMetadata( *project, + if (!(EditActions::DoEditMetadata( *project, _("Edit Metadata Tags"), _("Exported Tags"), mProject->GetShowId3Dialog()))) { return false; diff --git a/src/menus/EditMenus.cpp b/src/menus/EditMenus.cpp index e69de29bb..e0506cf06 100644 --- a/src/menus/EditMenus.cpp +++ b/src/menus/EditMenus.cpp @@ -0,0 +1,1228 @@ +#include "../HistoryWindow.h" +#include "../LabelTrack.h" +#include "../Menus.h" +#include "../MixerBoard.h" +#include "../NoteTrack.h" +#include "../Prefs.h" +#include "../Project.h" +#include "../Tags.h" +#include "../TimeTrack.h" +#include "../TrackPanel.h" +#include "../UndoManager.h" +#include "../WaveTrack.h" +#include "../commands/CommandContext.h" +#include "../commands/CommandManager.h" +#include "../commands/ScreenshotCommand.h" +#include "../prefs/PrefsDialog.h" +#include "../prefs/SpectrogramSettings.h" +#include "../prefs/WaveformSettings.h" +#include "../widgets/Ruler.h" + +// private helper classes and functions +namespace { +void FinishCopy + (const Track *n, Track::Holder &&dest, TrackList &list) +{ + Track::FinishCopy( n, dest.get() ); + if (dest) + list.Add(std::move(dest)); +} + +// Handle text paste (into active label), if any. Return true if did paste. +// (This was formerly the first part of overly-long OnPaste.) +bool DoPasteText(AudacityProject &project) +{ + auto tracks = project.GetTracks(); + auto trackPanel = project.GetTrackPanel(); + auto &selectedRegion = project.GetViewInfo().selectedRegion; + + for (auto pLabelTrack : tracks->Any()) + { + // Does this track have an active label? + if (pLabelTrack->IsSelected()) { + + // Yes, so try pasting into it + if (pLabelTrack->PasteSelectedText(selectedRegion.t0(), + selectedRegion.t1())) + { + project.PushState(_("Pasted text from the clipboard"), _("Paste")); + + // Make sure caret is in view + int x; + if (pLabelTrack->CalcCursorX(&x)) { + trackPanel->ScrollIntoView(x); + } + + // Redraw everyting (is that necessary???) and bail + project.RedrawProject(); + return true; + } + } + } + return false; +} + +// Return true if nothing selected, regardless of paste result. +// If nothing was selected, create and paste into NEW tracks. +// (This was formerly the second part of overly-long OnPaste.) +bool DoPasteNothingSelected(AudacityProject &project) +{ + auto tracks = project.GetTracks(); + auto trackFactory = project.GetTrackFactory(); + auto trackPanel = project.GetTrackPanel(); + auto &selectedRegion = project.GetViewInfo().selectedRegion; + + // First check whether anything's selected. + if (tracks->Selected()) + return false; + else + { + auto clipTrackRange = AudacityProject::msClipboard->Any< const Track >(); + if (clipTrackRange.empty()) + return true; // nothing to paste + + Track* pFirstNewTrack = NULL; + for (auto pClip : clipTrackRange) { + Maybe locker; + + Track::Holder uNewTrack; + Track *pNewTrack; + pClip->TypeSwitch( + [&](const WaveTrack *wc) { + if ((AudacityProject::msClipProject != &project)) + // Cause duplication of block files on disk, when copy is + // between projects + locker.create(wc); + uNewTrack = trackFactory->NewWaveTrack( + wc->GetSampleFormat(), wc->GetRate()), + pNewTrack = uNewTrack.get(); + }, +#ifdef USE_MIDI + [&](const NoteTrack *) { + uNewTrack = trackFactory->NewNoteTrack(), + pNewTrack = uNewTrack.get(); + }, +#endif + [&](const LabelTrack *) { + uNewTrack = trackFactory->NewLabelTrack(), + pNewTrack = uNewTrack.get(); + }, + [&](const TimeTrack *) { + // Maintain uniqueness of the time track! + pNewTrack = tracks->GetTimeTrack(); + if (!pNewTrack) + uNewTrack = trackFactory->NewTimeTrack(), + pNewTrack = uNewTrack.get(); + } + ); + + wxASSERT(pClip); + + pNewTrack->Paste(0.0, pClip); + + if (!pFirstNewTrack) + pFirstNewTrack = pNewTrack; + + pNewTrack->SetSelected(true); + if (uNewTrack) + FinishCopy(pClip, std::move(uNewTrack), *tracks); + else + Track::FinishCopy(pClip, pNewTrack); + } + + // Select some pasted samples, which is probably impossible to get right + // with various project and track sample rates. + // So do it at the sample rate of the project + AudacityProject *p = GetActiveProject(); + double projRate = p->GetRate(); + double quantT0 = QUANTIZED_TIME(AudacityProject::msClipT0, projRate); + double quantT1 = QUANTIZED_TIME(AudacityProject::msClipT1, projRate); + selectedRegion.setTimes( + 0.0, // anywhere else and this should be + // half a sample earlier + quantT1 - quantT0); + + project.PushState(_("Pasted from the clipboard"), _("Paste")); + + project.RedrawProject(); + + if (pFirstNewTrack) + trackPanel->EnsureVisible(pFirstNewTrack); + + return true; + } +} + +} + +namespace EditActions { + +// exported helper functions + +void DoReloadPreferences( AudacityProject &project ) +{ + { + SpectrogramSettings::defaults().LoadPrefs(); + WaveformSettings::defaults().LoadPrefs(); + + GlobalPrefsDialog dialog(&project /* parent */ ); + wxCommandEvent Evt; + //dialog.Show(); + dialog.OnOK(Evt); + } + + // LL: Moved from PrefsDialog since wxWidgets on OSX can't deal with + // rebuilding the menus while the PrefsDialog is still in the modal + // state. + for (size_t i = 0; i < gAudacityProjects.size(); i++) { + AudacityProject *p = gAudacityProjects[i].get(); + + GetMenuManager(*p).RebuildMenuBar(*p); + p->RebuildOtherMenus(); +// TODO: The comment below suggests this workaround is obsolete. +#if defined(__WXGTK__) + // Workaround for: + // + // http://bugzilla.audacityteam.org/show_bug.cgi?id=458 + // + // This workaround should be removed when Audacity updates to wxWidgets + // 3.x which has a fix. + wxRect r = p->GetRect(); + p->SetSize(wxSize(1,1)); + p->SetSize(r.GetSize()); +#endif + } +} + +bool DoEditMetadata +(AudacityProject &project, + const wxString &title, const wxString &shortUndoDescription, bool force) +{ + auto tags = project.GetTags(); + + // Back up my tags + auto newTags = tags->Duplicate(); + + if (newTags->ShowEditDialog(&project, title, force)) { + if (*tags != *newTags) { + // Commit the change to project state only now. + project.SetTags( newTags ); + project.PushState(title, shortUndoDescription); + } + + return true; + } + + return false; +} + +void DoUndo(AudacityProject &project) +{ + auto trackPanel = project.GetTrackPanel(); + auto &undoManager = *project.GetUndoManager(); + auto &selectedRegion = project.GetViewInfo().selectedRegion; + auto mixerBoard = project.GetMixerBoard(); + auto historyWindow = project.GetHistoryWindow(); + + if (!project.UndoAvailable()) { + AudacityMessageBox(_("Nothing to undo")); + return; + } + + // can't undo while dragging + if (trackPanel->IsMouseCaptured()) { + return; + } + + const UndoState &state = undoManager.Undo(&selectedRegion); + project.PopState(state); + + trackPanel->EnsureVisible(trackPanel->GetFirstSelectedTrack()); + + project.RedrawProject(); + + if (historyWindow) + historyWindow->UpdateDisplay(); + + if (mixerBoard) + // Mixer board may need to change for selection state and pan/gain + mixerBoard->Refresh(); + + MenuManager::ModifyUndoMenuItems(project); +} + +// Menu handler functions + +struct Handler : CommandHandlerObject { + +void OnUndo(const CommandContext &context) +{ + DoUndo(context.project); +} + +void OnRedo(const CommandContext &context) +{ + auto &project = context.project; + auto trackPanel = project.GetTrackPanel(); + auto &undoManager = *project.GetUndoManager(); + auto &selectedRegion = project.GetViewInfo().selectedRegion; + auto mixerBoard = project.GetMixerBoard(); + auto historyWindow = project.GetHistoryWindow(); + + if (!project.RedoAvailable()) { + AudacityMessageBox(_("Nothing to redo")); + return; + } + // Can't redo whilst dragging + if (trackPanel->IsMouseCaptured()) { + return; + } + + const UndoState &state = undoManager.Redo(&selectedRegion); + project.PopState(state); + + trackPanel->EnsureVisible(trackPanel->GetFirstSelectedTrack()); + + project.RedrawProject(); + + if (historyWindow) + historyWindow->UpdateDisplay(); + + if (mixerBoard) + // Mixer board may need to change for selection state and pan/gain + mixerBoard->Refresh(); + + MenuManager::ModifyUndoMenuItems(project); +} + +void OnCut(const CommandContext &context) +{ + auto &project = context.project; + auto tracks = project.GetTracks(); + auto trackPanel = project.GetTrackPanel(); + auto &selectedRegion = project.GetViewInfo().selectedRegion; + auto ruler = project.GetRulerPanel(); + auto historyWindow = project.GetHistoryWindow(); + + // This doesn't handle cutting labels, it handles + // cutting the _text_ inside of labels, i.e. if you're + // in the middle of editing the label text and select "Cut". + + for (auto lt : tracks->Selected< LabelTrack >()) { + if (lt->CutSelectedText()) { + trackPanel->Refresh(false); + return; + } + } + + AudacityProject::ClearClipboard(); + + auto pNewClipboard = TrackList::Create(); + auto &newClipboard = *pNewClipboard; + + tracks->Selected().Visit( +#if defined(USE_MIDI) + [&](NoteTrack *n) { + // Since portsmf has a built-in cut operator, we use that instead + auto dest = n->Cut(selectedRegion.t0(), + selectedRegion.t1()); + FinishCopy(n, std::move(dest), newClipboard); + }, +#endif + [&](Track *n) { + auto dest = n->Copy(selectedRegion.t0(), + selectedRegion.t1()); + FinishCopy(n, std::move(dest), newClipboard); + } + ); + + // Survived possibility of exceptions. Commit changes to the clipboard now. + newClipboard.Swap(*AudacityProject::msClipboard); + + // Proceed to change the project. If this throws, the project will be + // rolled back by the top level handler. + + (tracks->Any() + &Track::IsSelectedOrSyncLockSelected).Visit( +#if defined(USE_MIDI) + [](NoteTrack*) { + //if NoteTrack, it was cut, so do not clear anything + + // PRL: But what if it was sync lock selected only, not selected? + }, +#endif + [&](WaveTrack *wt, const Track::Fallthrough &fallthrough) { + if (gPrefs->Read(wxT("/GUI/EnableCutLines"), (long)0)) { + wt->ClearAndAddCutLine( + selectedRegion.t0(), + selectedRegion.t1()); + } + else + fallthrough(); + }, + [&](Track *n) { + n->Clear(selectedRegion.t0(), + selectedRegion.t1()); + } + ); + + AudacityProject::msClipT0 = selectedRegion.t0(); + AudacityProject::msClipT1 = selectedRegion.t1(); + AudacityProject::msClipProject = &project; + + selectedRegion.collapseToT0(); + + project.PushState(_("Cut to the clipboard"), _("Cut")); + + // Bug 1663 + //mRuler->ClearPlayRegion(); + ruler->DrawOverlays( true ); + + project.RedrawProject(); + + if (historyWindow) + historyWindow->UpdateDisplay(); +} + +void OnDelete(const CommandContext &context) +{ + auto &project = context.project; + project.Clear(); +} + +void OnCopy(const CommandContext &context) +{ + auto &project = context.project; + auto tracks = project.GetTracks(); + auto trackPanel = project.GetTrackPanel(); + auto &selectedRegion = project.GetViewInfo().selectedRegion; + auto historyWindow = project.GetHistoryWindow(); + + for (auto lt : tracks->Selected< LabelTrack >()) { + if (lt->CopySelectedText()) { + //trackPanel->Refresh(false); + return; + } + } + + AudacityProject::ClearClipboard(); + + auto pNewClipboard = TrackList::Create(); + auto &newClipboard = *pNewClipboard; + + for (auto n : tracks->Selected()) { + auto dest = n->Copy(selectedRegion.t0(), + selectedRegion.t1()); + FinishCopy(n, std::move(dest), newClipboard); + } + + // Survived possibility of exceptions. Commit changes to the clipboard now. + newClipboard.Swap(*AudacityProject::msClipboard); + + AudacityProject::msClipT0 = selectedRegion.t0(); + AudacityProject::msClipT1 = selectedRegion.t1(); + AudacityProject::msClipProject = &project; + + //Make sure the menus/toolbar states get updated + trackPanel->Refresh(false); + + if (historyWindow) + historyWindow->UpdateDisplay(); +} + +void OnPaste(const CommandContext &context) +{ + auto &project = context.project; + auto tracks = project.GetTracks(); + auto trackPanel = project.GetTrackPanel(); + auto trackFactory = project.GetTrackFactory(); + auto &selectedRegion = project.GetViewInfo().selectedRegion; + auto isSyncLocked = project.IsSyncLocked(); + + // Handle text paste (into active label) first. + if (DoPasteText(project)) + return; + + // If nothing's selected, we just insert NEW tracks. + if (DoPasteNothingSelected(project)) + return; + + auto clipTrackRange = AudacityProject::msClipboard->Any< const Track >(); + if (clipTrackRange.empty()) + return; + + // Otherwise, paste into the selected tracks. + double t0 = selectedRegion.t0(); + double t1 = selectedRegion.t1(); + + auto pN = tracks->Any().begin(); + + Track *ff = NULL; + const Track *lastClipBeforeMismatch = NULL; + const Track *mismatchedClip = NULL; + const Track *prevClip = NULL; + + bool bAdvanceClipboard = true; + bool bPastedSomething = false; + + auto pC = clipTrackRange.begin(); + size_t nnChannels=0, ncChannels=0; + while (*pN && *pC) { + auto n = *pN; + auto c = *pC; + if (n->GetSelected()) { + bAdvanceClipboard = true; + if (mismatchedClip) + c = mismatchedClip; + if (!c->SameKindAs(*n)) { + if (!mismatchedClip) { + lastClipBeforeMismatch = prevClip; + mismatchedClip = c; + } + bAdvanceClipboard = false; + c = lastClipBeforeMismatch; + + + // If the types still don't match... + while (c && !c->SameKindAs(*n)) { + prevClip = c; + c = * ++ pC; + } + } + + // Handle case where the first track in clipboard + // is of different type than the first selected track + if (!c) { + c = mismatchedClip; + while (n && (!c->SameKindAs(*n) || !n->GetSelected())) + { + // Must perform sync-lock adjustment before incrementing n + if (n->IsSyncLockSelected()) { + auto newT1 = t0 + + (AudacityProject::msClipT1 - AudacityProject::msClipT0); + if (t1 != newT1 && t1 <= n->GetEndTime()) { + n->SyncLockAdjust(t1, newT1); + bPastedSomething = true; + } + } + n = * ++ pN; + } + if (!n) + c = NULL; + } + + // The last possible case for cross-type pastes: triggered when we try + // to paste 1+ tracks from one type into 1+ tracks of another type. If + // there's a mix of types, this shouldn't run. + if (!c) + // Throw, so that any previous changes to the project in this loop + // are discarded. + throw SimpleMessageBoxException{ + _("Pasting one type of track into another is not allowed.") + }; + + // We should need this check only each time we visit the leading + // channel + if ( n->IsLeader() ) { + wxASSERT( c->IsLeader() ); // the iteration logic should ensure this + + auto cChannels = TrackList::Channels(c); + ncChannels = cChannels.size(); + auto nChannels = TrackList::Channels(n); + nnChannels = nChannels.size(); + + // When trying to copy from stereo to mono track, show error and + // exit + // TODO: Automatically offer user to mix down to mono (unfortunately + // this is not easy to implement + if (ncChannels > nnChannels) + { + if (ncChannels > 2) { + // TODO: more-than-two-channels-message + // Re-word the error message + } + // else + + // Throw, so that any previous changes to the project in this + // loop are discarded. + throw SimpleMessageBoxException{ + _("Copying stereo audio into a mono track is not allowed.") + }; + } + } + + if (!ff) + ff = n; + + wxASSERT( n && c && n->SameKindAs(*c) ); + Maybe locker; + + n->TypeSwitch( + [&](WaveTrack *wn){ + const auto wc = static_cast(c); + if (AudacityProject::msClipProject != &project) + // Cause duplication of block files on disk, when copy is + // between projects + locker.create(wc); + bPastedSomething = true; + wn->ClearAndPaste(t0, t1, wc, true, true); + }, + [&](LabelTrack *ln){ + // Per Bug 293, users expect labels to move on a paste into + // a label track. + ln->Clear(t0, t1); + + ln->ShiftLabelsOnInsert( + AudacityProject::msClipT1 - AudacityProject::msClipT0, t0); + + bPastedSomething |= ln->PasteOver(t0, c); + }, + [&](Track *){ + bPastedSomething = true; + n->Clear(t0, t1); + n->Paste(t0, c); + } + ); + + --nnChannels; + --ncChannels; + + // When copying from mono to stereo track, paste the wave form + // to both channels + // TODO: more-than-two-channels + // This will replicate the last pasted channel as many times as needed + while (nnChannels > 0 && ncChannels == 0) + { + n = * ++ pN; + --nnChannels; + + n->TypeSwitch( + [&](WaveTrack *wn){ + bPastedSomething = true; + // Note: rely on locker being still be in scope! + wn->ClearAndPaste(t0, t1, c, true, true); + }, + [&](Track *){ + n->Clear(t0, t1); + bPastedSomething = true; + n->Paste(t0, c); + } + ); + } + + if (bAdvanceClipboard) { + prevClip = c; + c = * ++ pC; + } + } // if (n->GetSelected()) + else if (n->IsSyncLockSelected()) + { + auto newT1 = t0 + + (AudacityProject::msClipT1 - AudacityProject::msClipT0); + if (t1 != newT1 && t1 <= n->GetEndTime()) { + n->SyncLockAdjust(t1, newT1); + bPastedSomething = true; + } + } + ++pN; + } + + // This block handles the cases where our clipboard is smaller + // than the amount of selected destination tracks. We take the + // last wave track, and paste that one into the remaining + // selected tracks. + if ( *pN && ! *pC ) + { + const auto wc = + *AudacityProject::msClipboard->Any< const WaveTrack >().rbegin(); + Maybe locker; + if (AudacityProject::msClipProject != &project && wc) + // Cause duplication of block files on disk, when copy is + // between projects + locker.create(static_cast(wc)); + + tracks->Any().StartingWith(*pN).Visit( + [&](WaveTrack *wt, const Track::Fallthrough &fallthrough) { + if (!wt->GetSelected()) + return fallthrough(); + + if (wc) { + bPastedSomething = true; + wt->ClearAndPaste(t0, t1, wc, true, true); + } + else { + auto tmp = trackFactory->NewWaveTrack( + wt->GetSampleFormat(), wt->GetRate()); + tmp->InsertSilence(0.0, + // MJS: Is this correct? + AudacityProject::msClipT1 - AudacityProject::msClipT0); + tmp->Flush(); + + bPastedSomething = true; + wt->ClearAndPaste(t0, t1, tmp.get(), true, true); + } + }, + [&](LabelTrack *lt, const Track::Fallthrough &fallthrough) { + if (!lt->GetSelected()) + return fallthrough(); + + lt->Clear(t0, t1); + + // As above, only shift labels if sync-lock is on. + if (isSyncLocked) + lt->ShiftLabelsOnInsert( + AudacityProject::msClipT1 - AudacityProject::msClipT0, t0); + }, + [&](Track *n) { + if (n->IsSyncLockSelected()) + n->SyncLockAdjust(t1, t0 + + AudacityProject::msClipT1 - AudacityProject::msClipT0); + } + ); + } + + // TODO: What if we clicked past the end of the track? + + if (bPastedSomething) + { + selectedRegion.setT1( + t0 + AudacityProject::msClipT1 - AudacityProject::msClipT0); + + project.PushState(_("Pasted from the clipboard"), _("Paste")); + + project.RedrawProject(); + + if (ff) + trackPanel->EnsureVisible(ff); + } +} + +void OnDuplicate(const CommandContext &context) +{ + auto &project = context.project; + auto tracks = project.GetTracks(); + auto &selectedRegion = project.GetViewInfo().selectedRegion; + + // This iteration is unusual because we add to the list inside the loop + auto range = tracks->Selected(); + auto last = *range.rbegin(); + for (auto n : range) { + // Make copies not for clipboard but for direct addition to the project + auto dest = n->Copy(selectedRegion.t0(), + selectedRegion.t1(), false); + dest->Init(*n); + dest->SetOffset(wxMax(selectedRegion.t0(), n->GetOffset())); + tracks->Add(std::move(dest)); + + // This break is really needed, else we loop infinitely + if (n == last) + break; + } + + project.PushState(_("Duplicated"), _("Duplicate")); + + project.RedrawProject(); +} + +void OnSplitCut(const CommandContext &context) +{ + auto &project = context.project; + auto tracks = project.GetTracks(); + auto &selectedRegion = project.GetViewInfo().selectedRegion; + auto historyWindow = project.GetHistoryWindow(); + + AudacityProject::ClearClipboard(); + + auto pNewClipboard = TrackList::Create(); + auto &newClipboard = *pNewClipboard; + + Track::Holder dest; + + tracks->Selected().Visit( + [&](WaveTrack *n) { + dest = n->SplitCut( + selectedRegion.t0(), + selectedRegion.t1()); + if (dest) + FinishCopy(n, std::move(dest), newClipboard); + }, + [&](Track *n) { + dest = n->Copy(selectedRegion.t0(), + selectedRegion.t1()); + n->Silence(selectedRegion.t0(), + selectedRegion.t1()); + if (dest) + FinishCopy(n, std::move(dest), newClipboard); + } + ); + + // Survived possibility of exceptions. Commit changes to the clipboard now. + newClipboard.Swap(*AudacityProject::msClipboard); + + AudacityProject::msClipT0 = selectedRegion.t0(); + AudacityProject::msClipT1 = selectedRegion.t1(); + AudacityProject::msClipProject = &project; + + project.PushState(_("Split-cut to the clipboard"), _("Split Cut")); + + project.RedrawProject(); + + if (historyWindow) + historyWindow->UpdateDisplay(); +} + +void OnSplitDelete(const CommandContext &context) +{ + auto &project = context.project; + auto tracks = project.GetTracks(); + auto &selectedRegion = project.GetViewInfo().selectedRegion; + + tracks->Selected().Visit( + [&](WaveTrack *wt) { + wt->SplitDelete(selectedRegion.t0(), + selectedRegion.t1()); + }, + [&](Track *n) { + n->Silence(selectedRegion.t0(), + selectedRegion.t1()); + } + ); + + project.PushState( + wxString::Format(_("Split-deleted %.2f seconds at t=%.2f"), + selectedRegion.duration(), + selectedRegion.t0()), + _("Split Delete")); + + project.RedrawProject(); +} + +void OnSilence(const CommandContext &context) +{ + auto &project = context.project; + auto tracks = project.GetTracks(); + auto trackPanel = project.GetTrackPanel(); + auto &selectedRegion = project.GetViewInfo().selectedRegion; + + for ( auto n : tracks->Selected< AudioTrack >() ) + n->Silence(selectedRegion.t0(), selectedRegion.t1()); + + project.PushState( + wxString::Format(_("Silenced selected tracks for %.2f seconds at %.2f"), + selectedRegion.duration(), + selectedRegion.t0()), + _("Silence")); + + trackPanel->Refresh(false); +} + +void OnTrim(const CommandContext &context) +{ + auto &project = context.project; + auto tracks = project.GetTracks(); + auto &selectedRegion = project.GetViewInfo().selectedRegion; + + if (selectedRegion.isPoint()) + return; + + tracks->Selected().Visit( +#ifdef USE_MIDI + [&](NoteTrack *nt) { + nt->Trim(selectedRegion.t0(), + selectedRegion.t1()); + }, +#endif + [&](WaveTrack *wt) { + //Delete the section before the left selector + wt->Trim(selectedRegion.t0(), + selectedRegion.t1()); + } + ); + + project.PushState( + wxString::Format( + _("Trim selected audio tracks from %.2f seconds to %.2f seconds"), + selectedRegion.t0(), selectedRegion.t1()), + _("Trim Audio")); + + project.RedrawProject(); +} + +void OnSplit(const CommandContext &context) +{ + auto &project = context.project; + auto tracks = project.GetTracks(); + auto trackPanel = project.GetTrackPanel(); + auto &selectedRegion = project.GetViewInfo().selectedRegion; + + double sel0 = selectedRegion.t0(); + double sel1 = selectedRegion.t1(); + + for (auto wt : tracks->Selected< WaveTrack >()) + wt->Split( sel0, sel1 ); + + project.PushState(_("Split"), _("Split")); + trackPanel->Refresh(false); +#if 0 +//ANSWER-ME: Do we need to keep this commented out OnSplit() code? +// This whole section no longer used... + /* + * Previous (pre-multiclip) implementation of "Split" command + * This does work only when a range is selected! + * + TrackListIterator iter(tracks); + + Track *n = iter.First(); + Track *dest; + + TrackList newTracks; + + while (n) { + if (n->GetSelected()) { + double sel0 = selectedRegion.t0(); + double sel1 = selectedRegion.t1(); + + dest = n->Copy(sel0, sel1); + dest->Init(*n); + dest->SetOffset(wxMax(sel0, n->GetOffset())); + + if (sel1 >= n->GetEndTime()) + n->Clear(sel0, sel1); + else if (sel0 <= n->GetOffset()) { + n->Clear(sel0, sel1); + n->SetOffset(sel1); + } else + n->Silence(sel0, sel1); + + newTracks.Add(dest); + } + n = iter.Next(); + } + + TrackListIterator nIter(&newTracks); + n = nIter.First(); + while (n) { + tracks->Add(n); + n = nIter.Next(); + } + + PushState(_("Split"), _("Split")); + + FixScrollbars(); + trackPanel->Refresh(false); + */ +#endif +} + +void OnSplitNew(const CommandContext &context) +{ + auto &project = context.project; + auto tracks = project.GetTracks(); + auto &selectedRegion = project.GetViewInfo().selectedRegion; + + Track::Holder dest; + + // This iteration is unusual because we add to the list inside the loop + auto range = tracks->Selected(); + auto last = *range.rbegin(); + for (auto track : range) { + track->TypeSwitch( + [&](WaveTrack *wt) { + // Clips must be aligned to sample positions or the NEW clip will + // not fit in the gap where it came from + double offset = wt->GetOffset(); + offset = wt->LongSamplesToTime(wt->TimeToLongSamples(offset)); + double newt0 = wt->LongSamplesToTime(wt->TimeToLongSamples( + selectedRegion.t0())); + double newt1 = wt->LongSamplesToTime(wt->TimeToLongSamples( + selectedRegion.t1())); + dest = wt->SplitCut(newt0, newt1); + if (dest) { + dest->SetOffset(wxMax(newt0, offset)); + FinishCopy(wt, std::move(dest), *tracks); + } + } +#if 0 + , + // LL: For now, just skip all non-wave tracks since the other do not + // yet support proper splitting. + [&](Track *n) { + dest = n->Cut(viewInfo.selectedRegion.t0(), + viewInfo.selectedRegion.t1()); + if (dest) { + dest->SetOffset(wxMax(0, n->GetOffset())); + FinishCopy(n, std::move(dest), *tracks); + } + } +#endif + ); + if (track == last) + break; + } + + project.PushState(_("Split to new track"), _("Split New")); + + project.RedrawProject(); +} + +void OnJoin(const CommandContext &context) +{ + auto &project = context.project; + auto tracks = project.GetTracks(); + auto &selectedRegion = project.GetViewInfo().selectedRegion; + + for (auto wt : tracks->Selected< WaveTrack >()) + wt->Join(selectedRegion.t0(), + selectedRegion.t1()); + + project.PushState( + wxString::Format(_("Joined %.2f seconds at t=%.2f"), + selectedRegion.duration(), + selectedRegion.t0()), + _("Join")); + + project.RedrawProject(); +} + +void OnDisjoin(const CommandContext &context) +{ + auto &project = context.project; + auto tracks = project.GetTracks(); + auto &selectedRegion = project.GetViewInfo().selectedRegion; + + for (auto wt : tracks->Selected< WaveTrack >()) + wt->Disjoin(selectedRegion.t0(), + selectedRegion.t1()); + + project.PushState( + wxString::Format(_("Detached %.2f seconds at t=%.2f"), + selectedRegion.duration(), + selectedRegion.t0()), + _("Detach")); + + project.RedrawProject(); +} + +void OnEditMetadata(const CommandContext &context) +{ + auto &project = context.project; + (void)DoEditMetadata( project, + _("Edit Metadata Tags"), _("Metadata Tags"), true); +} + +void OnPreferences(const CommandContext &context) +{ + auto &project = context.project; + + GlobalPrefsDialog dialog(&project /* parent */ ); + + if( ScreenshotCommand::MayCapture( &dialog ) ) + return; + + if (!dialog.ShowModal()) { + // Canceled + return; + } + + // LL: Moved from PrefsDialog since wxWidgets on OSX can't deal with + // rebuilding the menus while the PrefsDialog is still in the modal + // state. + for (size_t i = 0; i < gAudacityProjects.size(); i++) { + AudacityProject *p = gAudacityProjects[i].get(); + + GetMenuManager(*p).RebuildMenuBar(*p); + p->RebuildOtherMenus(); +// TODO: The comment below suggests this workaround is obsolete. +#if defined(__WXGTK__) + // Workaround for: + // + // http://bugzilla.audacityteam.org/show_bug.cgi?id=458 + // + // This workaround should be removed when Audacity updates to wxWidgets + // 3.x which has a fix. + wxRect r = p->GetRect(); + p->SetSize(wxSize(1,1)); + p->SetSize(r.GetSize()); +#endif + } +} + +// Legacy functions, not used as of version 2.3.0 + +#if 0 +void OnPasteOver(const CommandContext &context) +{ + auto &project = context.project; + auto &selectedRegion = project.GetViewInfo().selectedRegion; + + if((AudacityProject::msClipT1 - AudacityProject::msClipT0) > 0.0) + { + selectedRegion.setT1( + selectedRegion.t0() + + (AudacityProject::msClipT1 - AudacityProject::msClipT0)); + // MJS: pointless, given what we do in OnPaste? + } + OnPaste(context); + + return; +} +#endif + +}; // struct Handler + +} // namespace + +static CommandHandlerObject &findCommandHandler(AudacityProject &) { + // Handler is not stateful. Doesn't need a factory registered with + // AudacityProject. + static EditActions::Handler instance; + return instance; +}; + +// Menu definitions + +#define FN(X) findCommandHandler, \ + static_cast(& EditActions::Handler :: X) +#define XXO(X) _(X), wxString{X}.Contains("...") + +MenuTable::BaseItemPtr LabelEditMenus( AudacityProject &project ); + +MenuTable::BaseItemPtr EditMenu( AudacityProject &project ) +{ + using namespace MenuTable; + using Options = CommandManager::Options; + + constexpr auto NotBusyTimeAndTracksFlags = + AudioIONotBusyFlag | TimeSelectedFlag | TracksSelectedFlag; + + // The default shortcut key for Redo is different on different platforms. + static constexpr auto redoKey = +#ifdef __WXMSW__ + wxT("Ctrl+Y") +#else + wxT("Ctrl+Shift+Z") +#endif + ; + + // The default shortcut key for Preferences is different on different + // platforms. + static constexpr auto prefKey = +#ifdef __WXMAC__ + wxT("Ctrl+,") +#else + wxT("Ctrl+P") +#endif + ; + + return Menu( _("&Edit"), + Command( wxT("Undo"), XXO("&Undo"), FN(OnUndo), + AudioIONotBusyFlag | UndoAvailableFlag, wxT("Ctrl+Z") ), + + Command( wxT("Redo"), XXO("&Redo"), FN(OnRedo), + AudioIONotBusyFlag | RedoAvailableFlag, redoKey ), + + Special( [](AudacityProject &project, wxMenu&) { + // Change names in the CommandManager as a side-effect + MenuManager::ModifyUndoMenuItems(project); + }), + + Separator(), + + // Basic Edit commands + /* i18n-hint: (verb)*/ + Command( wxT("Cut"), XXO("Cu&t"), FN(OnCut), + AudioIONotBusyFlag | CutCopyAvailableFlag | NoAutoSelect, + Options{ wxT("Ctrl+X") } + .Mask( AudioIONotBusyFlag | CutCopyAvailableFlag ) ), + Command( wxT("Delete"), XXO("&Delete"), FN(OnDelete), + AudioIONotBusyFlag | NoAutoSelect, + Options{ wxT("Ctrl+K") } + .Mask( AudioIONotBusyFlag ) ), + /* i18n-hint: (verb)*/ + Command( wxT("Copy"), XXO("&Copy"), FN(OnCopy), + AudioIONotBusyFlag | CutCopyAvailableFlag, wxT("Ctrl+C") ), + /* i18n-hint: (verb)*/ + Command( wxT("Paste"), XXO("&Paste"), FN(OnPaste), + AudioIONotBusyFlag, wxT("Ctrl+V") ), + /* i18n-hint: (verb)*/ + Command( wxT("Duplicate"), XXO("Duplic&ate"), FN(OnDuplicate), + NotBusyTimeAndTracksFlags, wxT("Ctrl+D") ), + + Separator(), + + Menu( _("R&emove Special"), + /* i18n-hint: (verb) Do a special kind of cut*/ + Command( wxT("SplitCut"), XXO("Spl&it Cut"), FN(OnSplitCut), + NotBusyTimeAndTracksFlags, wxT("Ctrl+Alt+X") ), + /* i18n-hint: (verb) Do a special kind of DELETE*/ + Command( wxT("SplitDelete"), XXO("Split D&elete"), FN(OnSplitDelete), + NotBusyTimeAndTracksFlags, wxT("Ctrl+Alt+K") ), + + Separator(), + + /* i18n-hint: (verb)*/ + Command( wxT("Silence"), XXO("Silence Audi&o"), FN(OnSilence), + AudioIONotBusyFlag | TimeSelectedFlag | AudioTracksSelectedFlag, + wxT("Ctrl+L") ), + /* i18n-hint: (verb)*/ + Command( wxT("Trim"), XXO("Tri&m Audio"), FN(OnTrim), + AudioIONotBusyFlag | TimeSelectedFlag | AudioTracksSelectedFlag, + wxT("Ctrl+T") ) + ), + + Separator(), + + ////////////////////////////////////////////////////////////////////////// + + Menu( _("Clip B&oundaries"), + /* i18n-hint: (verb) It's an item on a menu. */ + Command( wxT("Split"), XXO("Sp&lit"), FN(OnSplit), + AudioIONotBusyFlag | WaveTracksSelectedFlag, wxT("Ctrl+I") ), + Command( wxT("SplitNew"), XXO("Split Ne&w"), FN(OnSplitNew), + AudioIONotBusyFlag | TimeSelectedFlag | WaveTracksSelectedFlag, + wxT("Ctrl+Alt+I") ), + + Separator(), + + /* i18n-hint: (verb)*/ + Command( wxT("Join"), XXO("&Join"), FN(OnJoin), + NotBusyTimeAndTracksFlags, wxT("Ctrl+J") ), + Command( wxT("Disjoin"), XXO("Detac&h at Silences"), FN(OnDisjoin), + NotBusyTimeAndTracksFlags, wxT("Ctrl+Alt+J") ) + ), + + ////////////////////////////////////////////////////////////////////////// + + LabelEditMenus, + + Command( wxT("EditMetaData"), XXO("&Metadata..."), FN(OnEditMetadata), + AudioIONotBusyFlag ), + + ////////////////////////////////////////////////////////////////////////// + +#ifndef __WXMAC__ + Separator(), +#endif + + Command( wxT("Preferences"), XXO("Pre&ferences..."), FN(OnPreferences), + AudioIONotBusyFlag, prefKey ) + ); +} + +MenuTable::BaseItemPtr ExtraEditMenu( AudacityProject & ) +{ + using namespace MenuTable; + using Options = CommandManager::Options; + constexpr auto flags = + AudioIONotBusyFlag | TracksSelectedFlag | TimeSelectedFlag; + return Menu( _("&Edit"), + Command( wxT("DeleteKey"), XXO("&Delete Key"), FN(OnDelete), + (flags | NoAutoSelect), + Options{ wxT("Backspace") }.Mask( flags ) ), + Command( wxT("DeleteKey2"), XXO("Delete Key&2"), FN(OnDelete), + (flags | NoAutoSelect), + Options{ wxT("Delete") }.Mask( flags ) ) + ); +} + +#undef XXO +#undef FN diff --git a/src/menus/LabelMenus.cpp b/src/menus/LabelMenus.cpp index e69de29bb..306b2a6e7 100644 --- a/src/menus/LabelMenus.cpp +++ b/src/menus/LabelMenus.cpp @@ -0,0 +1,500 @@ +#include "../AudioIO.h" +#include "../LabelTrack.h" +#include "../Menus.h" +#include "../Prefs.h" +#include "../Project.h" +#include "../TrackPanel.h" +#include "../WaveTrack.h" +#include "../commands/CommandContext.h" +#include "../commands/CommandManager.h" + +// private helper classes and functions +namespace { + +//Adds label and returns index of label in labeltrack. +int DoAddLabel( + AudacityProject &project, const SelectedRegion ®ion, + bool preserveFocus = false) +{ + auto tracks = project.GetTracks(); + auto trackPanel = project.GetTrackPanel(); + auto trackFactory = project.GetTrackFactory(); + + wxString title; // of label + + bool useDialog; + gPrefs->Read(wxT("/GUI/DialogForNameNewLabel"), &useDialog, false); + if (useDialog) { + if (LabelTrack::DialogForLabelName( + project, region, wxEmptyString, title) == wxID_CANCEL) + return -1; // index + } + + // If the focused track is a label track, use that + Track *const pFocusedTrack = trackPanel->GetFocusedTrack(); + + // Look for a label track at or after the focused track + auto iter = pFocusedTrack + ? tracks->Find(pFocusedTrack) + : tracks->Any().begin(); + auto lt = * iter.Filter< LabelTrack >(); + + // If none found, start a NEW label track and use it + if (!lt) { + lt = static_cast + (tracks->Add(trackFactory->NewLabelTrack())); + } + +// LLL: Commented as it seemed a little forceful to remove users +// selection when adding the label. This does not happen if +// you select several tracks and the last one of those is a +// label track...typing a label will not clear the selections. +// +// SelectNone(); + lt->SetSelected(true); + + int focusTrackNumber; + if (useDialog) { + focusTrackNumber = -2; + } + else { + focusTrackNumber = -1; + if (pFocusedTrack && preserveFocus) { + // Must remember the track to re-focus after finishing a label edit. + // do NOT identify it by a pointer, which might dangle! Identify + // by position. + focusTrackNumber = pFocusedTrack->GetIndex(); + } + } + + int index = lt->AddLabel(region, title, focusTrackNumber); + + project.PushState(_("Added label"), _("Label")); + + project.RedrawProject(); + if (!useDialog) { + trackPanel->EnsureVisible(lt); + } + trackPanel->SetFocus(); + + return index; +} + +} + +namespace LabelEditActions { + +// exported helper functions +// none + +// Menu handler functions + +struct Handler : CommandHandlerObject { + +void OnEditLabels(const CommandContext &context) +{ + auto &project = context.project; + LabelTrack::DoEditLabels(project); +} + +void OnAddLabel(const CommandContext &context) +{ + auto &project = context.project; + auto &selectedRegion = project.GetViewInfo().selectedRegion; + + DoAddLabel(project, selectedRegion); +} + +void OnAddLabelPlaying(const CommandContext &context) +{ + auto &project = context.project; + auto token = project.GetAudioIOToken(); + + if (token > 0 && + gAudioIO->IsStreamActive(token)) { + double indicator = gAudioIO->GetStreamTime(); + DoAddLabel(project, SelectedRegion(indicator, indicator), true); + } +} + +// Creates a NEW label in each selected label track with text from the system +// clipboard +void OnPasteNewLabel(const CommandContext &context) +{ + auto &project = context.project; + auto tracks = project.GetTracks(); + auto trackFactory = project.GetTrackFactory(); + auto trackPanel = project.GetTrackPanel(); + auto &selectedRegion = project.GetViewInfo().selectedRegion; + + bool bPastedSomething = false; + + { + auto trackRange = tracks->Selected< const LabelTrack >(); + if (trackRange.empty()) + { + // If there are no selected label tracks, try to choose the first label + // track after some other selected track + Track *t = *tracks->Selected().begin() + .Filter( &Track::Any ) + .Filter(); + + // If no match found, add one + if (!t) { + t = tracks->Add(trackFactory->NewLabelTrack()); + } + + // Select this track so the loop picks it up + t->SetSelected(true); + } + } + + LabelTrack *plt = NULL; // the previous track + for ( auto lt : tracks->Selected< LabelTrack >() ) + { + // Unselect the last label, so we'll have just one active label when + // we're done + if (plt) + plt->Unselect(); + + // Add a NEW label, paste into it + // Paul L: copy whatever defines the selected region, not just times + lt->AddLabel(selectedRegion); + if (lt->PasteSelectedText(selectedRegion.t0(), + selectedRegion.t1())) + bPastedSomething = true; + + // Set previous track + plt = lt; + } + + // plt should point to the last label track pasted to -- ensure it's visible + // and set focus + if (plt) { + trackPanel->EnsureVisible(plt); + trackPanel->SetFocus(); + } + + if (bPastedSomething) { + project.PushState( + _("Pasted from the clipboard"), _("Paste Text to New Label")); + + // Is this necessary? (carried over from former logic in OnPaste()) + project.RedrawProject(); + } +} + +void OnToggleTypeToCreateLabel(const CommandContext &WXUNUSED(context) ) +{ + bool typeToCreateLabel; + gPrefs->Read(wxT("/GUI/TypeToCreateLabel"), &typeToCreateLabel, true); + gPrefs->Write(wxT("/GUI/TypeToCreateLabel"), !typeToCreateLabel); + gPrefs->Flush(); + MenuManager::ModifyAllProjectToolbarMenus(); +} + +void OnCutLabels(const CommandContext &context) +{ + auto &project = context.project; + auto &selectedRegion = project.GetViewInfo().selectedRegion; + + if( selectedRegion.isPoint() ) + return; + + // Because of grouping the copy may need to operate on different tracks than + // the clear, so we do these actions separately. + project.EditClipboardByLabel( &WaveTrack::CopyNonconst ); + + if( gPrefs->Read( wxT( "/GUI/EnableCutLines" ), ( long )0 ) ) + project.EditByLabel( &WaveTrack::ClearAndAddCutLine, true ); + else + project.EditByLabel( &WaveTrack::Clear, true ); + + AudacityProject::msClipProject = &project; + + selectedRegion.collapseToT0(); + + project.PushState( + /* i18n-hint: (verb) past tense. Audacity has just cut the labeled audio + regions.*/ + _( "Cut labeled audio regions to clipboard" ), + /* i18n-hint: (verb)*/ + _( "Cut Labeled Audio" ) ); + + project.RedrawProject(); +} + +void OnDeleteLabels(const CommandContext &context) +{ + auto &project = context.project; + auto &selectedRegion = project.GetViewInfo().selectedRegion; + + if( selectedRegion.isPoint() ) + return; + + project.EditByLabel( &WaveTrack::Clear, true ); + + selectedRegion.collapseToT0(); + + project.PushState( + /* i18n-hint: (verb) Audacity has just deleted the labeled audio regions*/ + _( "Deleted labeled audio regions" ), + /* i18n-hint: (verb)*/ + _( "Delete Labeled Audio" ) ); + + project.RedrawProject(); +} + +void OnSplitCutLabels(const CommandContext &context) +{ + auto &project = context.project; + auto &selectedRegion = project.GetViewInfo().selectedRegion; + + if( selectedRegion.isPoint() ) + return; + + project.EditClipboardByLabel( &WaveTrack::SplitCut ); + + AudacityProject::msClipProject = &project; + + project.PushState( + /* i18n-hint: (verb) Audacity has just split cut the labeled audio + regions*/ + _( "Split Cut labeled audio regions to clipboard" ), + /* i18n-hint: (verb) Do a special kind of cut on the labels*/ + _( "Split Cut Labeled Audio" ) ); + + project.RedrawProject(); +} + +void OnSplitDeleteLabels(const CommandContext &context) +{ + auto &project = context.project; + auto &selectedRegion = project.GetViewInfo().selectedRegion; + + if( selectedRegion.isPoint() ) + return; + + project.EditByLabel( &WaveTrack::SplitDelete, false ); + + project.PushState( + /* i18n-hint: (verb) Audacity has just done a special kind of DELETE on + the labeled audio regions */ + _( "Split Deleted labeled audio regions" ), + /* i18n-hint: (verb) Do a special kind of DELETE on labeled audio + regions */ + _( "Split Delete Labeled Audio" ) ); + + project.RedrawProject(); +} + +void OnSilenceLabels(const CommandContext &context) +{ + auto &project = context.project; + auto trackPanel = project.GetTrackPanel(); + auto &selectedRegion = project.GetViewInfo().selectedRegion; + + if( selectedRegion.isPoint() ) + return; + + project.EditByLabel( &WaveTrack::Silence, false ); + + project.PushState( + /* i18n-hint: (verb)*/ + _( "Silenced labeled audio regions" ), + /* i18n-hint: (verb)*/ + _( "Silence Labeled Audio" ) ); + + trackPanel->Refresh( false ); +} + +void OnCopyLabels(const CommandContext &context) +{ + auto &project = context.project; + auto trackPanel = project.GetTrackPanel(); + auto &selectedRegion = project.GetViewInfo().selectedRegion; + + if( selectedRegion.isPoint() ) + return; + + project.EditClipboardByLabel( &WaveTrack::CopyNonconst ); + + AudacityProject::msClipProject = &project; + + project.PushState( _( "Copied labeled audio regions to clipboard" ), + /* i18n-hint: (verb)*/ + _( "Copy Labeled Audio" ) ); + + trackPanel->Refresh( false ); +} + +void OnSplitLabels(const CommandContext &context) +{ + auto &project = context.project; + + project.EditByLabel( &WaveTrack::Split, false ); + + project.PushState( + /* i18n-hint: (verb) past tense. Audacity has just split the labeled + audio (a point or a region)*/ + _( "Split labeled audio (points or regions)" ), + /* i18n-hint: (verb)*/ + _( "Split Labeled Audio" ) ); + + project.RedrawProject(); +} + +void OnJoinLabels(const CommandContext &context) +{ + auto &project = context.project; + auto &selectedRegion = project.GetViewInfo().selectedRegion; + + if( selectedRegion.isPoint() ) + return; + + project.EditByLabel( &WaveTrack::Join, false ); + + project.PushState( + /* i18n-hint: (verb) Audacity has just joined the labeled audio (points or + regions) */ + _( "Joined labeled audio (points or regions)" ), + /* i18n-hint: (verb) */ + _( "Join Labeled Audio" ) ); + + project.RedrawProject(); +} + +void OnDisjoinLabels(const CommandContext &context) +{ + auto &project = context.project; + auto &selectedRegion = project.GetViewInfo().selectedRegion; + + if( selectedRegion.isPoint() ) + return; + + project.EditByLabel( &WaveTrack::Disjoin, false ); + + project.PushState( + /* i18n-hint: (verb) Audacity has just detached the labeled audio regions. + This message appears in history and tells you about something + Audacity has done.*/ + _( "Detached labeled audio regions" ), + /* i18n-hint: (verb)*/ + _( "Detach Labeled Audio" ) ); + + project.RedrawProject(); +} + +}; // struct Handler + +} // namespace + +static CommandHandlerObject &findCommandHandler(AudacityProject &) { + // Handler is not stateful. Doesn't need a factory registered with + // AudacityProject. + static LabelEditActions::Handler instance; + return instance; +}; + +// Menu definitions + +#define FN(X) findCommandHandler, \ + static_cast(& LabelEditActions::Handler :: X) +#define XXO(X) _(X), wxString{X}.Contains("...") + +MenuTable::BaseItemPtr LabelEditMenus( AudacityProject &project ) +{ + using namespace MenuTable; + using Options = CommandManager::Options; + + static const auto checkOff = Options{}.CheckState( false ); + + constexpr auto NotBusyLabelsAndWaveFlags = + AudioIONotBusyFlag | + LabelsSelectedFlag | WaveTracksExistFlag | TimeSelectedFlag; + + // Returns TWO menus. + + return Items( + + Menu( _("&Labels"), + Command( wxT("EditLabels"), XXO("&Edit Labels..."), FN(OnEditLabels), + AudioIONotBusyFlag ), + + Separator(), + + Command( wxT("AddLabel"), XXO("Add Label at &Selection"), + FN(OnAddLabel), AlwaysEnabledFlag, wxT("Ctrl+B") ), + Command( wxT("AddLabelPlaying"), + XXO("Add Label at &Playback Position"), + FN(OnAddLabelPlaying), AudioIOBusyFlag, +#ifdef __WXMAC__ + wxT("Ctrl+.") +#else + wxT("Ctrl+M") +#endif + ), + Command( wxT("PasteNewLabel"), XXO("Paste Te&xt to New Label"), + FN(OnPasteNewLabel), + AudioIONotBusyFlag, wxT("Ctrl+Alt+V") ), + + Separator(), + + Command( wxT("TypeToCreateLabel"), + XXO("&Type to Create a Label (on/off)"), + FN(OnToggleTypeToCreateLabel), AlwaysEnabledFlag, checkOff ) + ), // first menu + + ///////////////////////////////////////////////////////////////////////////// + + Menu( _("La&beled Audio"), + /* i18n-hint: (verb)*/ + Command( wxT("CutLabels"), XXO("&Cut"), FN(OnCutLabels), + AudioIONotBusyFlag | LabelsSelectedFlag | WaveTracksExistFlag | + TimeSelectedFlag | IsNotSyncLockedFlag, + Options{ wxT("Alt+X"), _("Label Cut") } ), + Command( wxT("DeleteLabels"), XXO("&Delete"), FN(OnDeleteLabels), + AudioIONotBusyFlag | LabelsSelectedFlag | WaveTracksExistFlag | + TimeSelectedFlag | IsNotSyncLockedFlag, + Options{ wxT("Alt+K"), _("Label Delete") } ), + + Separator(), + + /* i18n-hint: (verb) A special way to cut out a piece of audio*/ + Command( wxT("SplitCutLabels"), XXO("&Split Cut"), + FN(OnSplitCutLabels), NotBusyLabelsAndWaveFlags, + Options{ wxT("Alt+Shift+X"), _("Label Split Cut") } ), + Command( wxT("SplitDeleteLabels"), XXO("Sp&lit Delete"), + FN(OnSplitDeleteLabels), NotBusyLabelsAndWaveFlags, + Options{ wxT("Alt+Shift+K"), _("Label Split Delete") } ), + + Separator(), + + Command( wxT("SilenceLabels"), XXO("Silence &Audio"), + FN(OnSilenceLabels), NotBusyLabelsAndWaveFlags, + Options{ wxT("Alt+L"), _("Label Silence") } ), + /* i18n-hint: (verb)*/ + Command( wxT("CopyLabels"), XXO("Co&py"), FN(OnCopyLabels), + NotBusyLabelsAndWaveFlags, + Options{ wxT("Alt+Shift+C"), _("Label Copy") } ), + + Separator(), + + /* i18n-hint: (verb)*/ + Command( wxT("SplitLabels"), XXO("Spli&t"), FN(OnSplitLabels), + AudioIONotBusyFlag | LabelsSelectedFlag | WaveTracksExistFlag, + Options{ wxT("Alt+I"), _("Label Split") } ), + /* i18n-hint: (verb)*/ + Command( wxT("JoinLabels"), XXO("&Join"), FN(OnJoinLabels), + NotBusyLabelsAndWaveFlags, + Options{ wxT("Alt+J"), _("Label Join") } ), + Command( wxT("DisjoinLabels"), XXO("Detac&h at Silences"), + FN(OnDisjoinLabels), NotBusyLabelsAndWaveFlags, + wxT("Alt+Shift+J") ) + ) // second menu + + ); // two menus +} + +#undef XXO +#undef FN diff --git a/src/widgets/HelpSystem.cpp b/src/widgets/HelpSystem.cpp index 2ca6d5a26..8e4a3bbf8 100644 --- a/src/widgets/HelpSystem.cpp +++ b/src/widgets/HelpSystem.cpp @@ -529,8 +529,7 @@ void QuickFixDialog::OnFix(wxCommandEvent &event) // This is overkill (aka slow), as all preferences are reloaded and all // toolbars recreated. // Overkill probably doesn't matter, as this command is infrequently used. - CommandContext context( project ); - GetMenuCommandHandler(project).OnReloadPreferences( context ); + EditActions::DoReloadPreferences( project ); } // Change the label after doing the fix, as the fix may take a second or two. @@ -542,4 +541,4 @@ void QuickFixDialog::OnFix(wxCommandEvent &event) wxButton * pWin = (wxButton*)FindWindowById( wxID_CANCEL ); if( pWin ) pWin->SetFocus( ); -} \ No newline at end of file +}