1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-06-17 00:20:06 +02:00

Redo aup3 import more simply; fix cut, copy, import of TimeTrack

This commit is contained in:
Paul Licameli 2021-02-02 15:24:28 -05:00
commit eb888bb03b
21 changed files with 176 additions and 436 deletions

View File

@ -41,16 +41,16 @@ cycles.
*/
// Really means, some track is selected, that isn't a time track
bool TracksSelectedPred( const AudacityProject &project )
// Strong predicate excludes tracks that do not support basic editing.
bool EditableTracksSelectedPred( const AudacityProject &project )
{
auto range = TrackList::Get( project ).Selected()
- []( const Track *pTrack ){
return track_cast<const TimeTrack*>( pTrack ); };
return !pTrack->SupportsBasicEditing(); };
return !range.empty();
};
// This predicate includes time tracks too.
// Weaker predicate.
bool AnyTracksSelectedPred( const AudacityProject &project )
{
auto range = TrackList::Get( project ).Selected();
@ -177,8 +177,8 @@ const ReservedCommandFlag&
CommandFlagOptions{}.DisableDefaultMessage()
}; return flag; }
const ReservedCommandFlag&
TracksSelectedFlag() { static ReservedCommandFlag flag{
TracksSelectedPred, // exclude TimeTracks
EditableTracksSelectedFlag() { static ReservedCommandFlag flag{
EditableTracksSelectedPred,
{ []( const TranslatableString &Name ){ return
// i18n-hint: %s will be replaced by the name of an action, such as "Remove Tracks".
XO("\"%s\" requires one or more tracks to be selected.").Format( Name );
@ -186,7 +186,7 @@ const ReservedCommandFlag&
}; return flag; }
const ReservedCommandFlag&
AnyTracksSelectedFlag() { static ReservedCommandFlag flag{
AnyTracksSelectedPred, // Allow TimeTracks
AnyTracksSelectedPred,
{ []( const TranslatableString &Name ){ return
// i18n-hint: %s will be replaced by the name of an action, such as "Remove Tracks".
XO("\"%s\" requires one or more tracks to be selected.").Format( Name );

View File

@ -15,7 +15,7 @@ Paul Licameli split from Menus.cpp
#include "commands/CommandFlag.h"
bool TracksSelectedPred( const AudacityProject &project );
bool EditableTracksSelectedPred( const AudacityProject &project );
bool AudioIOBusyPred( const AudacityProject &project );
bool TimeSelectedPred( const AudacityProject &project );
extern const CommandFlagOptions &cutCopyOptions();
@ -27,7 +27,7 @@ extern AUDACITY_DLL_API const ReservedCommandFlag
&TimeSelectedFlag(), // This is equivalent to check if there is a valid selection, so it's used for Zoom to Selection too
&WaveTracksSelectedFlag(),
&TracksExistFlag(),
&TracksSelectedFlag(),
&EditableTracksSelectedFlag(),
&AnyTracksSelectedFlag(),
&TrackPanelHasFocus(); //lll

View File

@ -85,6 +85,13 @@ LabelTrack::LabelTrack(const LabelTrack &orig) :
}
}
Track::Holder LabelTrack::PasteInto( AudacityProject & ) const
{
auto pNewTrack = std::make_shared<LabelTrack>();
pNewTrack->Paste(0.0, this);
return pNewTrack;
}
template<typename IntervalType>
static IntervalType DoMakeInterval(const LabelStruct &label, size_t index)
{

View File

@ -155,6 +155,8 @@ public:
int FindNextLabel(const SelectedRegion& currentSelection);
int FindPrevLabel(const SelectedRegion& currentSelection);
Track::Holder PasteInto( AudacityProject & ) const override;
struct IntervalData final : Track::IntervalData {
size_t index;
explicit IntervalData(size_t index) : index{index} {};

View File

@ -683,6 +683,13 @@ QuantizedTimeAndBeat NoteTrack::NearestBeatTime( double time ) const
return { seq_time + GetOffset(), beat };
}
Track::Holder NoteTrack::PasteInto( AudacityProject & ) const
{
auto pNewTrack = std::make_shared<NoteTrack>();
pNewTrack->Paste(0.0, this);
return pNewTrack;
}
auto NoteTrack::GetIntervals() const -> ConstIntervals
{
ConstIntervals results;

View File

@ -185,6 +185,8 @@ public:
mVisibleChannels = CHANNEL_BIT(c);
}
Track::Holder PasteInto( AudacityProject & ) const override;
ConstIntervals GetIntervals() const override;
Intervals GetIntervals() override;

View File

@ -27,7 +27,6 @@ Paul Licameli split from AudacityProject.cpp
#include "SampleBlock.h"
#include "Tags.h"
#include "TempDirectory.h"
#include "TimeTrack.h"
#include "ViewInfo.h"
#include "WaveTrack.h"
#include "widgets/AudacityMessageBox.h"
@ -1677,357 +1676,6 @@ bool ProjectFileIO::WriteDoc(const char *table,
return true;
}
// Importing an AUP3 project into an AUP3 project is a bit different than
// normal importing since we need to copy data from one DB to the other
// while adjusting the sample block IDs to represent the newly assigned
// IDs.
bool ProjectFileIO::ImportProject(const FilePath &fileName)
{
// Get access to the current project file
auto db = DB();
bool success = false;
bool restore = true;
int rc = SQLITE_OK;
// Ensure the inbound database gets detached
auto detach = finally([&]
{
// If this DETACH fails, subsequent project imports will fail until Audacity
// is relaunched.
auto result = sqlite3_exec(db, "DETACH DATABASE inbound;", nullptr, nullptr, nullptr);
// Only capture the error if there wasn't a previous error
if (result != SQLITE_OK && (rc == SQLITE_DONE || rc == SQLITE_OK))
{
SetDBError(
XO("Failed to detach project during import")
);
}
});
// Attach the inbound project file
wxString sql;
sql.Printf("ATTACH DATABASE 'file:%s?immutable=1&mode=ro' AS inbound;", fileName);
rc = sqlite3_exec(db, sql, nullptr, nullptr, nullptr);
if (rc != SQLITE_OK)
{
SetDBError(
XO("Unable to attach %s project file").Format(fileName)
);
return false;
}
// We need either the autosave or project docs from the inbound AUP3
wxMemoryBuffer buffer;
// Get the autosave doc, if any
if (!GetBlob("SELECT dict || doc FROM inbound.project WHERE id = 1;", buffer))
{
// Error already set
return false;
}
// If we didn't have an autosave doc, load the project doc instead
if (buffer.GetDataLen() == 0)
{
if (!GetBlob("SELECT dict || doc FROM inbound.autosave WHERE id = 1;", buffer))
{
// Error already set
return false;
}
// Missing both the autosave and project docs. This can happen if the
// system were to crash before the first autosave into a temporary file.
if (buffer.GetDataLen() == 0)
{
SetError(XO("Unable to load project or autosave documents"));
return false;
}
}
wxString project;
project = ProjectSerializer::Decode(buffer);
if (project.size() == 0)
{
SetError(XO("Unable to decode project document"));
return false;
}
// Parse the project doc
wxStringInputStream in(project);
wxXmlDocument doc;
if (!doc.Load(in))
{
SetError(XO("Unable to parse the project document"));
return false;
}
// Get the root ("project") node
wxXmlNode *root = doc.GetRoot();
if (!root->GetName().IsSameAs(wxT("project")))
{
SetError(XO("Missing root node in project document"));
return false;
}
// Soft delete all non-essential attributes to prevent updating the active
// project. This takes advantage of the knowledge that when a project is
// parsed, unrecognized attributes are simply ignored.
//
// This is necessary because we don't want any of the active project settings
// to be modified by the inbound project.
for (wxXmlAttribute *attr = root->GetAttributes(); attr; attr = attr->GetNext())
{
wxString name = attr->GetName();
if (!name.IsSameAs(wxT("version")) && !name.IsSameAs(wxT("audacityversion")))
{
attr->SetName(name + wxT("_deleted"));
}
}
// Recursively find and collect all waveblock nodes
std::vector<wxXmlNode *> blocknodes;
std::function<void(wxXmlNode *)> findblocks = [&](wxXmlNode *node)
{
while (node)
{
if (node->GetName().IsSameAs(wxT("waveblock")))
{
blocknodes.push_back(node);
}
else
{
findblocks(node->GetChildren());
}
node = node->GetNext();
}
};
// Get access to the active tracklist
auto pProject = &mProject;
auto &tracklist = TrackList::Get(*pProject);
// Search for a timetrack and remove it if the project already has one
if (*tracklist.Any<TimeTrack>().begin())
{
// Find a timetrack and remove it if it exists
for (wxXmlNode *node = doc.GetRoot()->GetChildren(); node; node = node->GetNext())
{
if (node->GetName().IsSameAs(wxT("timetrack")))
{
AudacityMessageBox(
XO("The active project already has a time track and one was encountered in the project being imported, bypassing imported time track."),
XO("Project Import"),
wxOK | wxICON_EXCLAMATION | wxCENTRE,
&GetProjectFrame(*pProject));
root->RemoveChild(node);
break;
}
}
}
// Find all waveblocks in all wavetracks
for (wxXmlNode *node = doc.GetRoot()->GetChildren(); node; node = node->GetNext())
{
if (node->GetName().IsSameAs(wxT("wavetrack")))
{
findblocks(node->GetChildren());
}
}
{
// Cleanup...
sqlite3_stmt *stmt = nullptr;
auto cleanup = finally([&]
{
// Ensure the prepared statement gets cleaned up
if (stmt)
{
// Ignore the return code since it will have already been captured below.
sqlite3_finalize(stmt);
}
});
// Prepare the statement to copy the sample block from the inbound project to the
// active project. All columns other than the blockid column get copied.
wxString columns(wxT("sampleformat, summin, summax, sumrms, summary256, summary64k, samples"));
sql.Printf("INSERT INTO main.sampleblocks (%s)"
" SELECT %s"
" FROM inbound.sampleblocks"
" WHERE blockid = ?;",
columns,
columns);
rc = sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr);
if (rc != SQLITE_OK)
{
SetDBError(
XO("Unable to prepare project file command:\n\n%s").Format(sql)
);
return false;
}
/* i18n-hint: This title appears on a dialog that indicates the progress
in doing something.*/
ProgressDialog progress(XO("Progress"), XO("Importing project"), pdlgHideStopButton);
ProgressResult result = ProgressResult::Success;
wxLongLong_t count = 0;
wxLongLong_t total = blocknodes.size();
rc = sqlite3_exec(db, "BEGIN;", nullptr, nullptr, nullptr);
if (rc != SQLITE_OK)
{
SetDBError(
XO("Unable to start a transaction during import")
);
return false;
}
// Copy all the sample blocks from the inbound project file into
// the active one, while remembering which were copied.
for (auto node : blocknodes)
{
// Find the blockid attribute...it should always be there
wxXmlAttribute *attr = node->GetAttributes();
while (attr && !attr->GetName().IsSameAs(wxT("blockid")))
{
attr = attr->GetNext();
}
wxASSERT(attr != nullptr);
// And get the blockid
SampleBlockID blockid;
attr->GetValue().ToLongLong(&blockid);
if ( blockid <= 0 )
// silent block
continue;
// Bind statement parameters
rc = sqlite3_bind_int64(stmt, 1, blockid);
if (rc != SQLITE_OK)
{
SetDBError(
XO("Failed to bind SQL parameter")
);
break;
}
// Process it
rc = sqlite3_step(stmt);
if (rc != SQLITE_DONE)
{
SetDBError(
XO("Failed to import sample block.\nThe following command failed:\n\n%s").Format(sql)
);
break;
}
// Replace the original blockid with the new one
attr->SetValue(wxString::Format(wxT("%lld"), sqlite3_last_insert_rowid(db)));
// Reset the statement for the next iteration
if (sqlite3_reset(stmt) != SQLITE_OK)
{
THROW_INCONSISTENCY_EXCEPTION;
}
// Remember that we copied this node in case the user cancels
result = progress.Update(++count, total);
if (result != ProgressResult::Success)
{
break;
}
}
// Bail if the import was cancelled or failed. If the user stopped the
// import or it completed, then we continue on.
if (rc != SQLITE_DONE || result == ProgressResult::Cancelled || result == ProgressResult::Failed)
{
// If this fails (probably due to memory or disk space), the transaction will
// (presumably) stil be active, so further updates to the project file will
// fail as well. Not really much we can do about it except tell the user.
auto result = sqlite3_exec(db, "ROLLBACK;", nullptr, nullptr, nullptr);
// Only capture the error if there wasn't a previous error
if (result != SQLITE_OK && rc == SQLITE_DONE)
{
SetDBError(
XO("Failed to rollback transaction during import")
);
}
return false;
}
// Go ahead and commit now. If this fails (probably due to memory or disk space),
// the transaction will (presumably) stil be active, so further updates to the
// project file will fail as well. Not really much we can do about it except tell
// the user.
rc = sqlite3_exec(db, "COMMIT;", nullptr, nullptr, nullptr);
if (rc != SQLITE_OK)
{
SetDBError(
XO("Unable to commit transaction during import")
);
return false;
}
// Copy over tags...likely to produce duplicates...needs work once used
rc = sqlite3_exec(db,
"INSERT INTO main.tags SELECT * FROM inbound.tags;",
nullptr,
nullptr,
nullptr);
if (rc != SQLITE_OK)
{
SetDBError(
XO("Failed to import tags")
);
return false;
}
}
// Recreate the project doc with the revisions we've made above. If this fails
// it's probably due to memory.
wxStringOutputStream output;
if (!doc.Save(output))
{
SetError(
XO("Unable to recreate project information.")
);
return false;
}
// Now load the document as normal
XMLFileReader xmlFile;
if (!xmlFile.ParseString(this, output.GetString()))
{
SetError(
XO("Unable to parse project information."), xmlFile.GetErrorStr()
);
return false;
}
return true;
}
bool ProjectFileIO::LoadProject(const FilePath &fileName, bool ignoreAutosave)
{
bool success = false;

View File

@ -92,7 +92,6 @@ public:
bool CloseProject();
bool ReopenProject();
bool ImportProject(const FilePath &fileName);
bool LoadProject(const FilePath &fileName, bool ignoreAutosave);
bool UpdateSaved(const TrackList *tracks = nullptr);
bool SaveProject(const FilePath &fileName, const TrackList *lastSaved);

View File

@ -1137,6 +1137,29 @@ ProjectFileManager::AddImportedTracks(const FilePath &fileName,
// HandleResize();
}
namespace {
bool ImportProject(AudacityProject &dest, const FilePath &fileName)
{
InvisibleTemporaryProject temp;
auto &project = temp.Project();
auto &projectFileIO = ProjectFileIO::Get(project);
if (!projectFileIO.LoadProject(fileName, false))
return false;
auto &srcTracks = TrackList::Get(project);
auto &destTracks = TrackList::Get(dest);
for (const Track *pTrack : srcTracks.Any()) {
auto destTrack = pTrack->PasteInto(dest);
Track::FinishCopy(pTrack, destTrack.get());
if (destTrack.use_count() == 1)
destTracks.Add(destTrack);
}
Tags::Get(dest).Merge(Tags::Get(project));
return true;
}
}
// If pNewTrackList is passed in non-NULL, it gets filled with the pointers to NEW tracks.
bool ProjectFileManager::Import(
const FilePath &fileName,
@ -1151,7 +1174,7 @@ bool ProjectFileManager::Import(
// Handle AUP3 ("project") files directly
if (fileName.AfterLast('.').IsSameAs(wxT("aup3"), false)) {
if (projectFileIO.ImportProject(fileName)) {
if (ImportProject(project, fileName)) {
auto &history = ProjectHistory::Get(project);
// If the project was clean and temporary (not permanently saved), then set

View File

@ -85,7 +85,7 @@ void SelectAllIfNone( AudacityProject &project )
{
auto &viewInfo = ViewInfo::Get( project );
auto flags = MenuManager::Get( project ).GetUpdateFlags();
if((flags & TracksSelectedFlag()).none() ||
if((flags & EditableTracksSelectedFlag()).none() ||
viewInfo.selectedRegion.isPoint())
DoSelectAllAudio( project );
}
@ -98,7 +98,7 @@ bool SelectAllIfNoneAndAllowed( AudacityProject &project )
auto &viewInfo = ViewInfo::Get( project );
auto flags = MenuManager::Get( project ).GetUpdateFlags();
if((flags & TracksSelectedFlag()).none() ||
if((flags & EditableTracksSelectedFlag()).none() ||
viewInfo.selectedRegion.isPoint()) {
if (!allowed) {
return false;

View File

@ -268,6 +268,13 @@ std::shared_ptr<Tags> Tags::Duplicate() const
return std::make_shared<Tags>(*this);
}
void Tags::Merge( const Tags &other )
{
for ( auto &pair : other.mMap ) {
SetTag( pair.first, pair.second );
}
}
Tags & Tags::operator=(const Tags & src)
{
mEditTitle = src.mEditTitle;

View File

@ -88,6 +88,8 @@ class AUDACITY_DLL_API Tags final
std::shared_ptr<Tags> Duplicate() const;
void Merge( const Tags &other );
Tags & operator= (const Tags & src );
bool ShowEditDialog(

View File

@ -52,6 +52,11 @@ static ProjectFileIORegistry::Entry registerFactory{
TimeTrack::TimeTrack(const ZoomInfo *zoomInfo):
Track()
, mZoomInfo(zoomInfo)
{
CleanState();
}
void TimeTrack::CleanState()
{
mEnvelope = std::make_unique<BoundedEnvelope>(true, TIMETRACK_MIN, TIMETRACK_MAX, 1.0);
@ -131,6 +136,31 @@ void TimeTrack::SetRangeUpper(double upper)
mEnvelope->SetRangeUpper( upper );
}
bool TimeTrack::SupportsBasicEditing() const
{
return false;
}
Track::Holder TimeTrack::PasteInto( AudacityProject &project ) const
{
// Maintain uniqueness of the time track!
std::shared_ptr<TimeTrack> pNewTrack;
if( auto pTrack = *TrackList::Get( project ).Any<TimeTrack>().begin() )
pNewTrack = pTrack->SharedPointer<TimeTrack>();
else
pNewTrack = std::make_shared<TimeTrack>( &ViewInfo::Get( project ) );
// Should come here only for .aup3 import, not for paste (because the
// track is skipped in cut/copy commands)
// And for import we agree to replace the track contents completely
pNewTrack->CleanState();
pNewTrack->Init(*this);
pNewTrack->Paste(0.0, this);
pNewTrack->SetRangeLower(this->GetRangeLower());
pNewTrack->SetRangeUpper(this->GetRangeUpper());
return pNewTrack;
}
Track::Holder TimeTrack::Cut( double t0, double t1 )
{
auto result = Copy( t0, t1, false );

View File

@ -40,6 +40,10 @@ class TimeTrack final : public Track {
virtual ~TimeTrack();
bool SupportsBasicEditing() const override;
Holder PasteInto( AudacityProject & ) const override;
Holder Cut( double t0, double t1 ) override;
Holder Copy( double t0, double t1, bool forClipboard ) const override;
void Clear(double t0, double t1) override;
@ -91,6 +95,8 @@ class TimeTrack final : public Track {
Ruler &GetRuler() const { return *mRuler; }
private:
void CleanState();
// Identifying the type of track
TrackKind GetKind() const override { return TrackKind::Time; }

View File

@ -1224,6 +1224,11 @@ std::shared_ptr<const Track> Track::SubstituteOriginalTrack() const
return SharedPointer();
}
bool Track::SupportsBasicEditing() const
{
return true;
}
auto Track::GetIntervals() const -> ConstIntervals
{
return {};

View File

@ -318,6 +318,15 @@ class AUDACITY_DLL_API Track /* not final */
using ConstInterval = ConstTrackInterval;
using ConstIntervals = std::vector< ConstInterval >;
//! Whether this track type implements cut-copy-paste; by default, true
virtual bool SupportsBasicEditing() const;
using Holder = std::shared_ptr<Track>;
//! Find or create the destination track for a paste, maybe in a different project
/*! @return A smart pointer to the track; its `use_count()` can tell whether it is new */
virtual Holder PasteInto( AudacityProject & ) const = 0;
//! Report times on the track where important intervals begin and end, for UI to snap to
/*!
Some intervals may be empty, and no ordering of the intervals is assumed.
@ -390,7 +399,6 @@ private:
void Init(const Track &orig);
using Holder = std::shared_ptr<Track>;
// public nonvirtual duplication function that invokes Clone():
virtual Holder Duplicate() const;

View File

@ -334,6 +334,15 @@ static Container MakeIntervals(const std::vector<WaveClipHolder> &clips)
return result;
}
Track::Holder WaveTrack::PasteInto( AudacityProject &project ) const
{
auto &trackFactory = WaveTrackFactory::Get( project );
auto &pSampleBlockFactory = trackFactory.GetSampleBlockFactory();
auto pNewTrack = EmptyCopy( pSampleBlockFactory );
pNewTrack->Paste(0.0, this);
return pNewTrack;
}
auto WaveTrack::GetIntervals() const -> ConstIntervals
{
return MakeIntervals<ConstIntervals>( mClips );

View File

@ -537,6 +537,8 @@ private:
std::shared_ptr<WaveClip> pClip;
};
Track::Holder PasteInto( AudacityProject & ) const override;
ConstIntervals GetIntervals() const override;
Intervals GetIntervals() override;

View File

@ -11,7 +11,6 @@
#include "../ProjectSettings.h"
#include "../ProjectWindow.h"
#include "../SelectUtilities.h"
#include "../TimeTrack.h"
#include "../TrackPanel.h"
#include "../TrackPanelAx.h"
#include "../UndoManager.h"
@ -76,8 +75,6 @@ bool DoPasteText(AudacityProject &project)
bool DoPasteNothingSelected(AudacityProject &project)
{
auto &tracks = TrackList::Get( project );
auto &trackFactory = WaveTrackFactory::Get( project );
auto &pSampleBlockFactory = trackFactory.GetSampleBlockFactory();
auto &selectedRegion = ViewInfo::Get( project ).selectedRegion;
auto &viewInfo = ViewInfo::Get( project );
auto &window = ProjectWindow::Get( project );
@ -94,44 +91,18 @@ bool DoPasteNothingSelected(AudacityProject &project)
Track* pFirstNewTrack = NULL;
for (auto pClip : clipTrackRange) {
Track::Holder uNewTrack;
Track *pNewTrack;
pClip->TypeSwitch(
[&](const WaveTrack *wc) {
uNewTrack = wc->EmptyCopy( pSampleBlockFactory );
pNewTrack = uNewTrack.get();
},
#ifdef USE_MIDI
[&](const NoteTrack *) {
uNewTrack = std::make_shared<NoteTrack>(),
pNewTrack = uNewTrack.get();
},
#endif
[&](const LabelTrack *) {
uNewTrack = std::make_shared<LabelTrack>(),
pNewTrack = uNewTrack.get();
},
[&](const TimeTrack *) {
// Maintain uniqueness of the time track!
pNewTrack = *tracks.Any<TimeTrack>().begin();
if (!pNewTrack)
uNewTrack = std::make_shared<TimeTrack>( &viewInfo ),
pNewTrack = uNewTrack.get();
}
);
auto pNewTrack = pClip->PasteInto( project );
bool newTrack = (pNewTrack.use_count() == 1);
wxASSERT(pClip);
pNewTrack->Paste(0.0, pClip);
if (!pFirstNewTrack)
pFirstNewTrack = pNewTrack;
pFirstNewTrack = pNewTrack.get();
pNewTrack->SetSelected(true);
if (uNewTrack)
FinishCopy(pClip, uNewTrack, tracks);
if (newTrack)
FinishCopy(pClip, pNewTrack, tracks);
else
Track::FinishCopy(pClip, pNewTrack);
Track::FinishCopy(pClip, pNewTrack.get());
}
// Select some pasted samples, which is probably impossible to get right
@ -263,10 +234,12 @@ void OnCut(const CommandContext &context)
},
#endif
[&](Track *n) {
if (n->SupportsBasicEditing()) {
auto dest = n->Copy(selectedRegion.t0(),
selectedRegion.t1());
FinishCopy(n, dest, newClipboard);
}
}
);
// Survived possibility of exceptions. Commit changes to the clipboard now.
@ -298,8 +271,8 @@ void OnCut(const CommandContext &context)
fallthrough();
},
[&](Track *n) {
n->Clear(selectedRegion.t0(),
selectedRegion.t1());
if (n->SupportsBasicEditing())
n->Clear(selectedRegion.t0(), selectedRegion.t1());
}
);
@ -321,6 +294,8 @@ void OnDelete(const CommandContext &context)
auto &window = ProjectWindow::Get( project );
for (auto n : tracks.Any()) {
if (!n->SupportsBasicEditing())
continue;
if (n->GetSelected() || n->IsSyncLockSelected()) {
n->Clear(selectedRegion.t0(), selectedRegion.t1());
}
@ -359,10 +334,12 @@ void OnCopy(const CommandContext &context)
auto &newClipboard = *pNewClipboard;
for (auto n : tracks.Selected()) {
if (n->SupportsBasicEditing()) {
auto dest = n->Copy(selectedRegion.t0(),
selectedRegion.t1());
FinishCopy(n, dest, newClipboard);
}
}
// Survived possibility of exceptions. Commit changes to the clipboard now.
clipboard.Assign( std::move( newClipboard ),
@ -643,6 +620,9 @@ void OnDuplicate(const CommandContext &context)
auto range = tracks.Selected();
auto last = *range.rbegin();
for (auto n : range) {
if (!n->SupportsBasicEditing())
continue;
// Make copies not for clipboard but for direct addition to the project
auto dest = n->Copy(selectedRegion.t0(),
selectedRegion.t1(), false);
@ -683,6 +663,7 @@ void OnSplitCut(const CommandContext &context)
FinishCopy(n, dest, newClipboard);
},
[&](Track *n) {
if (n->SupportsBasicEditing()) {
dest = n->Copy(selectedRegion.t0(),
selectedRegion.t1());
n->Silence(selectedRegion.t0(),
@ -690,6 +671,7 @@ void OnSplitCut(const CommandContext &context)
if (dest)
FinishCopy(n, dest, newClipboard);
}
}
);
// Survived possibility of exceptions. Commit changes to the clipboard now.
@ -713,6 +695,7 @@ void OnSplitDelete(const CommandContext &context)
selectedRegion.t1());
},
[&](Track *n) {
if (n->SupportsBasicEditing())
n->Silence(selectedRegion.t0(),
selectedRegion.t1());
}
@ -1004,7 +987,7 @@ static const ReservedCommandFlag
if (
TimeSelectedPred( project )
&&
TracksSelectedPred( project )
EditableTracksSelectedPred( project )
)
return true;
@ -1020,7 +1003,7 @@ BaseItemSharedPtr EditMenu()
using Options = CommandManager::Options;
static const auto NotBusyTimeAndTracksFlags =
AudioIONotBusyFlag() | TimeSelectedFlag() | TracksSelectedFlag();
AudioIONotBusyFlag() | TimeSelectedFlag() | EditableTracksSelectedFlag();
// The default shortcut key for Redo is different on different platforms.
static constexpr auto redoKey =
@ -1065,7 +1048,7 @@ BaseItemSharedPtr EditMenu()
AudioIONotBusyFlag() | CutCopyAvailableFlag() | NoAutoSelect(),
wxT("Ctrl+X") ),
Command( wxT("Delete"), XXO("&Delete"), FN(OnDelete),
AudioIONotBusyFlag() | TracksSelectedFlag() | TimeSelectedFlag() | NoAutoSelect(),
AudioIONotBusyFlag() | EditableTracksSelectedFlag() | TimeSelectedFlag() | NoAutoSelect(),
wxT("Ctrl+K") ),
/* i18n-hint: (verb)*/
Command( wxT("Copy"), XXO("&Copy"), FN(OnCopy),
@ -1160,7 +1143,7 @@ BaseItemSharedPtr ExtraEditMenu()
{
using Options = CommandManager::Options;
static const auto flags =
AudioIONotBusyFlag() | TracksSelectedFlag() | TimeSelectedFlag();
AudioIONotBusyFlag() | EditableTracksSelectedFlag() | TimeSelectedFlag();
static BaseItemSharedPtr menu{
( FinderScope{ findCommandHandler },
Menu( wxT("Edit"), XXO("&Edit"),
@ -1184,7 +1167,7 @@ auto selectAll = []( AudacityProject &project, CommandFlag flagsRqd ){
RegisteredMenuItemEnabler selectTracks{{
[]{ return TracksExistFlag(); },
[]{ return TracksSelectedFlag(); },
[]{ return EditableTracksSelectedFlag(); },
canSelectAll,
selectAll
}};

View File

@ -483,7 +483,7 @@ void OnSelectSyncLockSel(const CommandContext &context)
auto &tracks = TrackList::Get( project );
bool selected = false;
for (auto t : tracks.Any()
for (auto t : tracks.Any() + &Track::SupportsBasicEditing
+ &Track::IsSyncLockSelected - &Track::IsSelected) {
t->SetSelected(true);
selected = true;
@ -866,7 +866,7 @@ void OnCursorTrackStart(const CommandContext &context)
double kWayOverToRight = std::numeric_limits<double>::max();
auto trackRange = tracks.Selected();
auto trackRange = tracks.Selected() + &Track::SupportsBasicEditing;
if (trackRange.empty())
// This should have been prevented by command manager
return;
@ -892,7 +892,7 @@ void OnCursorTrackEnd(const CommandContext &context)
double kWayOverToLeft = std::numeric_limits<double>::lowest();
auto trackRange = tracks.Selected();
auto trackRange = tracks.Selected() + &Track::SupportsBasicEditing;
if (trackRange.empty())
// This should have been prevented by command manager
return;
@ -1059,7 +1059,7 @@ BaseItemSharedPtr SelectMenu()
,
Command( wxT("SelSyncLockTracks"), XXO("In All &Sync-Locked Tracks"),
FN(OnSelectSyncLockSel),
TracksSelectedFlag() | IsSyncLockedFlag(),
EditableTracksSelectedFlag() | IsSyncLockedFlag(),
Options{ wxT("Ctrl+Shift+Y"), XO("Select Sync-Locked") } )
#endif
),
@ -1131,7 +1131,7 @@ BaseItemSharedPtr SelectMenu()
Section( "",
Command( wxT("ZeroCross"), XXO("At &Zero Crossings"),
FN(OnZeroCrossing), TracksSelectedFlag(),
FN(OnZeroCrossing), EditableTracksSelectedFlag(),
Options{ wxT("Z"), XO("Select Zero Crossing") } )
)
) ) };
@ -1216,11 +1216,11 @@ BaseItemSharedPtr CursorMenu()
Command( wxT("CursTrackStart"), XXO("Track &Start"),
FN(OnCursorTrackStart),
TracksSelectedFlag(),
EditableTracksSelectedFlag(),
Options{ wxT("J"), XO("Cursor to Track Start") } ),
Command( wxT("CursTrackEnd"), XXO("Track &End"),
FN(OnCursorTrackEnd),
TracksSelectedFlag(),
EditableTracksSelectedFlag(),
Options{ wxT("K"), XO("Cursor to Track End") } ),
Command( wxT("CursProjectStart"), XXO("&Project Start"),

View File

@ -1356,9 +1356,9 @@ BaseItemSharedPtr TracksMenu()
Command( wxT("UnmuteAllTracks"), XXO("&Unmute All Tracks"),
FN(OnUnmuteAllTracks), TracksExistFlag(), wxT("Ctrl+Shift+U") ),
Command( wxT("MuteTracks"), XXO("Mut&e Tracks"),
FN(OnMuteSelectedTracks), TracksSelectedFlag(), wxT("Ctrl+Alt+U") ),
FN(OnMuteSelectedTracks), EditableTracksSelectedFlag(), wxT("Ctrl+Alt+U") ),
Command( wxT("UnmuteTracks"), XXO("U&nmute Tracks"),
FN(OnUnmuteSelectedTracks), TracksSelectedFlag(), wxT("Ctrl+Alt+Shift+U") )
FN(OnUnmuteSelectedTracks), EditableTracksSelectedFlag(), wxT("Ctrl+Alt+Shift+U") )
),
Menu( wxT("Pan"), XXO("&Pan"),
@ -1367,13 +1367,13 @@ BaseItemSharedPtr TracksMenu()
// in the project could very easily be lost unless we
// require the tracks to be selected.
Command( wxT("PanLeft"), XXO("&Left"), FN(OnPanLeft),
TracksSelectedFlag(),
EditableTracksSelectedFlag(),
Options{}.LongName( XO("Pan Left") ) ),
Command( wxT("PanRight"), XXO("&Right"), FN(OnPanRight),
TracksSelectedFlag(),
EditableTracksSelectedFlag(),
Options{}.LongName( XO("Pan Right") ) ),
Command( wxT("PanCenter"), XXO("&Center"), FN(OnPanCenter),
TracksSelectedFlag(),
EditableTracksSelectedFlag(),
Options{}.LongName( XO("Pan Center") ) )
)
),
@ -1387,14 +1387,14 @@ BaseItemSharedPtr TracksMenu()
{ wxT("EndToEnd"), XXO("&Align End to End") },
{ wxT("Together"), XXO("Align &Together") },
},
FN(OnAlignNoSync), AudioIONotBusyFlag() | TracksSelectedFlag())
FN(OnAlignNoSync), AudioIONotBusyFlag() | EditableTracksSelectedFlag())
),
Section( "",
// Alignment commands using selection or zero
CommandGroup(wxT("Align"),
alignLabels(),
FN(OnAlign), AudioIONotBusyFlag() | TracksSelectedFlag())
FN(OnAlign), AudioIONotBusyFlag() | EditableTracksSelectedFlag())
),
Section( "",
@ -1411,7 +1411,7 @@ BaseItemSharedPtr TracksMenu()
// Do we need this sub-menu at all?
Menu( wxT("MoveSelectionAndTracks"), XO("Move Sele&ction and Tracks"), {
CommandGroup(wxT("AlignMove"), alignLabels(),
FN(OnAlignMoveSel), AudioIONotBusyFlag() | TracksSelectedFlag()),
FN(OnAlignMoveSel), AudioIONotBusyFlag() | EditableTracksSelectedFlag()),
} ),
#endif