mirror of
https://github.com/cookiengineer/audacity
synced 2025-08-14 15:11:13 +02:00
Redo aup3 import more simply; fix cut, copy, import of TimeTrack
This commit is contained in:
commit
eb888bb03b
@ -41,16 +41,16 @@ cycles.
|
|||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Really means, some track is selected, that isn't a time track
|
// Strong predicate excludes tracks that do not support basic editing.
|
||||||
bool TracksSelectedPred( const AudacityProject &project )
|
bool EditableTracksSelectedPred( const AudacityProject &project )
|
||||||
{
|
{
|
||||||
auto range = TrackList::Get( project ).Selected()
|
auto range = TrackList::Get( project ).Selected()
|
||||||
- []( const Track *pTrack ){
|
- []( const Track *pTrack ){
|
||||||
return track_cast<const TimeTrack*>( pTrack ); };
|
return !pTrack->SupportsBasicEditing(); };
|
||||||
return !range.empty();
|
return !range.empty();
|
||||||
};
|
};
|
||||||
|
|
||||||
// This predicate includes time tracks too.
|
// Weaker predicate.
|
||||||
bool AnyTracksSelectedPred( const AudacityProject &project )
|
bool AnyTracksSelectedPred( const AudacityProject &project )
|
||||||
{
|
{
|
||||||
auto range = TrackList::Get( project ).Selected();
|
auto range = TrackList::Get( project ).Selected();
|
||||||
@ -177,8 +177,8 @@ const ReservedCommandFlag&
|
|||||||
CommandFlagOptions{}.DisableDefaultMessage()
|
CommandFlagOptions{}.DisableDefaultMessage()
|
||||||
}; return flag; }
|
}; return flag; }
|
||||||
const ReservedCommandFlag&
|
const ReservedCommandFlag&
|
||||||
TracksSelectedFlag() { static ReservedCommandFlag flag{
|
EditableTracksSelectedFlag() { static ReservedCommandFlag flag{
|
||||||
TracksSelectedPred, // exclude TimeTracks
|
EditableTracksSelectedPred,
|
||||||
{ []( const TranslatableString &Name ){ return
|
{ []( const TranslatableString &Name ){ return
|
||||||
// i18n-hint: %s will be replaced by the name of an action, such as "Remove Tracks".
|
// 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 );
|
XO("\"%s\" requires one or more tracks to be selected.").Format( Name );
|
||||||
@ -186,7 +186,7 @@ const ReservedCommandFlag&
|
|||||||
}; return flag; }
|
}; return flag; }
|
||||||
const ReservedCommandFlag&
|
const ReservedCommandFlag&
|
||||||
AnyTracksSelectedFlag() { static ReservedCommandFlag flag{
|
AnyTracksSelectedFlag() { static ReservedCommandFlag flag{
|
||||||
AnyTracksSelectedPred, // Allow TimeTracks
|
AnyTracksSelectedPred,
|
||||||
{ []( const TranslatableString &Name ){ return
|
{ []( const TranslatableString &Name ){ return
|
||||||
// i18n-hint: %s will be replaced by the name of an action, such as "Remove Tracks".
|
// 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 );
|
XO("\"%s\" requires one or more tracks to be selected.").Format( Name );
|
||||||
|
@ -15,7 +15,7 @@ Paul Licameli split from Menus.cpp
|
|||||||
|
|
||||||
#include "commands/CommandFlag.h"
|
#include "commands/CommandFlag.h"
|
||||||
|
|
||||||
bool TracksSelectedPred( const AudacityProject &project );
|
bool EditableTracksSelectedPred( const AudacityProject &project );
|
||||||
bool AudioIOBusyPred( const AudacityProject &project );
|
bool AudioIOBusyPred( const AudacityProject &project );
|
||||||
bool TimeSelectedPred( const AudacityProject &project );
|
bool TimeSelectedPred( const AudacityProject &project );
|
||||||
extern const CommandFlagOptions &cutCopyOptions();
|
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
|
&TimeSelectedFlag(), // This is equivalent to check if there is a valid selection, so it's used for Zoom to Selection too
|
||||||
&WaveTracksSelectedFlag(),
|
&WaveTracksSelectedFlag(),
|
||||||
&TracksExistFlag(),
|
&TracksExistFlag(),
|
||||||
&TracksSelectedFlag(),
|
&EditableTracksSelectedFlag(),
|
||||||
&AnyTracksSelectedFlag(),
|
&AnyTracksSelectedFlag(),
|
||||||
&TrackPanelHasFocus(); //lll
|
&TrackPanelHasFocus(); //lll
|
||||||
|
|
||||||
|
@ -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>
|
template<typename IntervalType>
|
||||||
static IntervalType DoMakeInterval(const LabelStruct &label, size_t index)
|
static IntervalType DoMakeInterval(const LabelStruct &label, size_t index)
|
||||||
{
|
{
|
||||||
|
@ -155,6 +155,8 @@ public:
|
|||||||
int FindNextLabel(const SelectedRegion& currentSelection);
|
int FindNextLabel(const SelectedRegion& currentSelection);
|
||||||
int FindPrevLabel(const SelectedRegion& currentSelection);
|
int FindPrevLabel(const SelectedRegion& currentSelection);
|
||||||
|
|
||||||
|
Track::Holder PasteInto( AudacityProject & ) const override;
|
||||||
|
|
||||||
struct IntervalData final : Track::IntervalData {
|
struct IntervalData final : Track::IntervalData {
|
||||||
size_t index;
|
size_t index;
|
||||||
explicit IntervalData(size_t index) : index{index} {};
|
explicit IntervalData(size_t index) : index{index} {};
|
||||||
|
@ -683,6 +683,13 @@ QuantizedTimeAndBeat NoteTrack::NearestBeatTime( double time ) const
|
|||||||
return { seq_time + GetOffset(), beat };
|
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
|
auto NoteTrack::GetIntervals() const -> ConstIntervals
|
||||||
{
|
{
|
||||||
ConstIntervals results;
|
ConstIntervals results;
|
||||||
|
@ -185,6 +185,8 @@ public:
|
|||||||
mVisibleChannels = CHANNEL_BIT(c);
|
mVisibleChannels = CHANNEL_BIT(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Track::Holder PasteInto( AudacityProject & ) const override;
|
||||||
|
|
||||||
ConstIntervals GetIntervals() const override;
|
ConstIntervals GetIntervals() const override;
|
||||||
Intervals GetIntervals() override;
|
Intervals GetIntervals() override;
|
||||||
|
|
||||||
|
@ -27,7 +27,6 @@ Paul Licameli split from AudacityProject.cpp
|
|||||||
#include "SampleBlock.h"
|
#include "SampleBlock.h"
|
||||||
#include "Tags.h"
|
#include "Tags.h"
|
||||||
#include "TempDirectory.h"
|
#include "TempDirectory.h"
|
||||||
#include "TimeTrack.h"
|
|
||||||
#include "ViewInfo.h"
|
#include "ViewInfo.h"
|
||||||
#include "WaveTrack.h"
|
#include "WaveTrack.h"
|
||||||
#include "widgets/AudacityMessageBox.h"
|
#include "widgets/AudacityMessageBox.h"
|
||||||
@ -1677,357 +1676,6 @@ bool ProjectFileIO::WriteDoc(const char *table,
|
|||||||
return true;
|
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 ProjectFileIO::LoadProject(const FilePath &fileName, bool ignoreAutosave)
|
||||||
{
|
{
|
||||||
bool success = false;
|
bool success = false;
|
||||||
|
@ -92,7 +92,6 @@ public:
|
|||||||
bool CloseProject();
|
bool CloseProject();
|
||||||
bool ReopenProject();
|
bool ReopenProject();
|
||||||
|
|
||||||
bool ImportProject(const FilePath &fileName);
|
|
||||||
bool LoadProject(const FilePath &fileName, bool ignoreAutosave);
|
bool LoadProject(const FilePath &fileName, bool ignoreAutosave);
|
||||||
bool UpdateSaved(const TrackList *tracks = nullptr);
|
bool UpdateSaved(const TrackList *tracks = nullptr);
|
||||||
bool SaveProject(const FilePath &fileName, const TrackList *lastSaved);
|
bool SaveProject(const FilePath &fileName, const TrackList *lastSaved);
|
||||||
|
@ -1137,6 +1137,29 @@ ProjectFileManager::AddImportedTracks(const FilePath &fileName,
|
|||||||
// HandleResize();
|
// 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.
|
// If pNewTrackList is passed in non-NULL, it gets filled with the pointers to NEW tracks.
|
||||||
bool ProjectFileManager::Import(
|
bool ProjectFileManager::Import(
|
||||||
const FilePath &fileName,
|
const FilePath &fileName,
|
||||||
@ -1151,7 +1174,7 @@ bool ProjectFileManager::Import(
|
|||||||
|
|
||||||
// Handle AUP3 ("project") files directly
|
// Handle AUP3 ("project") files directly
|
||||||
if (fileName.AfterLast('.').IsSameAs(wxT("aup3"), false)) {
|
if (fileName.AfterLast('.').IsSameAs(wxT("aup3"), false)) {
|
||||||
if (projectFileIO.ImportProject(fileName)) {
|
if (ImportProject(project, fileName)) {
|
||||||
auto &history = ProjectHistory::Get(project);
|
auto &history = ProjectHistory::Get(project);
|
||||||
|
|
||||||
// If the project was clean and temporary (not permanently saved), then set
|
// If the project was clean and temporary (not permanently saved), then set
|
||||||
|
@ -85,7 +85,7 @@ void SelectAllIfNone( AudacityProject &project )
|
|||||||
{
|
{
|
||||||
auto &viewInfo = ViewInfo::Get( project );
|
auto &viewInfo = ViewInfo::Get( project );
|
||||||
auto flags = MenuManager::Get( project ).GetUpdateFlags();
|
auto flags = MenuManager::Get( project ).GetUpdateFlags();
|
||||||
if((flags & TracksSelectedFlag()).none() ||
|
if((flags & EditableTracksSelectedFlag()).none() ||
|
||||||
viewInfo.selectedRegion.isPoint())
|
viewInfo.selectedRegion.isPoint())
|
||||||
DoSelectAllAudio( project );
|
DoSelectAllAudio( project );
|
||||||
}
|
}
|
||||||
@ -98,7 +98,7 @@ bool SelectAllIfNoneAndAllowed( AudacityProject &project )
|
|||||||
auto &viewInfo = ViewInfo::Get( project );
|
auto &viewInfo = ViewInfo::Get( project );
|
||||||
auto flags = MenuManager::Get( project ).GetUpdateFlags();
|
auto flags = MenuManager::Get( project ).GetUpdateFlags();
|
||||||
|
|
||||||
if((flags & TracksSelectedFlag()).none() ||
|
if((flags & EditableTracksSelectedFlag()).none() ||
|
||||||
viewInfo.selectedRegion.isPoint()) {
|
viewInfo.selectedRegion.isPoint()) {
|
||||||
if (!allowed) {
|
if (!allowed) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -268,6 +268,13 @@ std::shared_ptr<Tags> Tags::Duplicate() const
|
|||||||
return std::make_shared<Tags>(*this);
|
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)
|
Tags & Tags::operator=(const Tags & src)
|
||||||
{
|
{
|
||||||
mEditTitle = src.mEditTitle;
|
mEditTitle = src.mEditTitle;
|
||||||
|
@ -88,6 +88,8 @@ class AUDACITY_DLL_API Tags final
|
|||||||
|
|
||||||
std::shared_ptr<Tags> Duplicate() const;
|
std::shared_ptr<Tags> Duplicate() const;
|
||||||
|
|
||||||
|
void Merge( const Tags &other );
|
||||||
|
|
||||||
Tags & operator= (const Tags & src );
|
Tags & operator= (const Tags & src );
|
||||||
|
|
||||||
bool ShowEditDialog(
|
bool ShowEditDialog(
|
||||||
|
@ -52,6 +52,11 @@ static ProjectFileIORegistry::Entry registerFactory{
|
|||||||
TimeTrack::TimeTrack(const ZoomInfo *zoomInfo):
|
TimeTrack::TimeTrack(const ZoomInfo *zoomInfo):
|
||||||
Track()
|
Track()
|
||||||
, mZoomInfo(zoomInfo)
|
, mZoomInfo(zoomInfo)
|
||||||
|
{
|
||||||
|
CleanState();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TimeTrack::CleanState()
|
||||||
{
|
{
|
||||||
mEnvelope = std::make_unique<BoundedEnvelope>(true, TIMETRACK_MIN, TIMETRACK_MAX, 1.0);
|
mEnvelope = std::make_unique<BoundedEnvelope>(true, TIMETRACK_MIN, TIMETRACK_MAX, 1.0);
|
||||||
|
|
||||||
@ -131,6 +136,31 @@ void TimeTrack::SetRangeUpper(double upper)
|
|||||||
mEnvelope->SetRangeUpper( 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 )
|
Track::Holder TimeTrack::Cut( double t0, double t1 )
|
||||||
{
|
{
|
||||||
auto result = Copy( t0, t1, false );
|
auto result = Copy( t0, t1, false );
|
||||||
|
@ -40,6 +40,10 @@ class TimeTrack final : public Track {
|
|||||||
virtual ~TimeTrack();
|
virtual ~TimeTrack();
|
||||||
|
|
||||||
|
|
||||||
|
bool SupportsBasicEditing() const override;
|
||||||
|
|
||||||
|
Holder PasteInto( AudacityProject & ) const override;
|
||||||
|
|
||||||
Holder Cut( double t0, double t1 ) override;
|
Holder Cut( double t0, double t1 ) override;
|
||||||
Holder Copy( double t0, double t1, bool forClipboard ) const override;
|
Holder Copy( double t0, double t1, bool forClipboard ) const override;
|
||||||
void Clear(double t0, double t1) override;
|
void Clear(double t0, double t1) override;
|
||||||
@ -91,6 +95,8 @@ class TimeTrack final : public Track {
|
|||||||
Ruler &GetRuler() const { return *mRuler; }
|
Ruler &GetRuler() const { return *mRuler; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void CleanState();
|
||||||
|
|
||||||
// Identifying the type of track
|
// Identifying the type of track
|
||||||
TrackKind GetKind() const override { return TrackKind::Time; }
|
TrackKind GetKind() const override { return TrackKind::Time; }
|
||||||
|
|
||||||
|
@ -1224,6 +1224,11 @@ std::shared_ptr<const Track> Track::SubstituteOriginalTrack() const
|
|||||||
return SharedPointer();
|
return SharedPointer();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Track::SupportsBasicEditing() const
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
auto Track::GetIntervals() const -> ConstIntervals
|
auto Track::GetIntervals() const -> ConstIntervals
|
||||||
{
|
{
|
||||||
return {};
|
return {};
|
||||||
|
10
src/Track.h
10
src/Track.h
@ -318,6 +318,15 @@ class AUDACITY_DLL_API Track /* not final */
|
|||||||
using ConstInterval = ConstTrackInterval;
|
using ConstInterval = ConstTrackInterval;
|
||||||
using ConstIntervals = std::vector< ConstInterval >;
|
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
|
//! 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.
|
Some intervals may be empty, and no ordering of the intervals is assumed.
|
||||||
@ -390,7 +399,6 @@ private:
|
|||||||
|
|
||||||
void Init(const Track &orig);
|
void Init(const Track &orig);
|
||||||
|
|
||||||
using Holder = std::shared_ptr<Track>;
|
|
||||||
// public nonvirtual duplication function that invokes Clone():
|
// public nonvirtual duplication function that invokes Clone():
|
||||||
virtual Holder Duplicate() const;
|
virtual Holder Duplicate() const;
|
||||||
|
|
||||||
|
@ -334,6 +334,15 @@ static Container MakeIntervals(const std::vector<WaveClipHolder> &clips)
|
|||||||
return result;
|
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
|
auto WaveTrack::GetIntervals() const -> ConstIntervals
|
||||||
{
|
{
|
||||||
return MakeIntervals<ConstIntervals>( mClips );
|
return MakeIntervals<ConstIntervals>( mClips );
|
||||||
|
@ -537,6 +537,8 @@ private:
|
|||||||
std::shared_ptr<WaveClip> pClip;
|
std::shared_ptr<WaveClip> pClip;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Track::Holder PasteInto( AudacityProject & ) const override;
|
||||||
|
|
||||||
ConstIntervals GetIntervals() const override;
|
ConstIntervals GetIntervals() const override;
|
||||||
Intervals GetIntervals() override;
|
Intervals GetIntervals() override;
|
||||||
|
|
||||||
|
@ -11,7 +11,6 @@
|
|||||||
#include "../ProjectSettings.h"
|
#include "../ProjectSettings.h"
|
||||||
#include "../ProjectWindow.h"
|
#include "../ProjectWindow.h"
|
||||||
#include "../SelectUtilities.h"
|
#include "../SelectUtilities.h"
|
||||||
#include "../TimeTrack.h"
|
|
||||||
#include "../TrackPanel.h"
|
#include "../TrackPanel.h"
|
||||||
#include "../TrackPanelAx.h"
|
#include "../TrackPanelAx.h"
|
||||||
#include "../UndoManager.h"
|
#include "../UndoManager.h"
|
||||||
@ -76,8 +75,6 @@ bool DoPasteText(AudacityProject &project)
|
|||||||
bool DoPasteNothingSelected(AudacityProject &project)
|
bool DoPasteNothingSelected(AudacityProject &project)
|
||||||
{
|
{
|
||||||
auto &tracks = TrackList::Get( project );
|
auto &tracks = TrackList::Get( project );
|
||||||
auto &trackFactory = WaveTrackFactory::Get( project );
|
|
||||||
auto &pSampleBlockFactory = trackFactory.GetSampleBlockFactory();
|
|
||||||
auto &selectedRegion = ViewInfo::Get( project ).selectedRegion;
|
auto &selectedRegion = ViewInfo::Get( project ).selectedRegion;
|
||||||
auto &viewInfo = ViewInfo::Get( project );
|
auto &viewInfo = ViewInfo::Get( project );
|
||||||
auto &window = ProjectWindow::Get( project );
|
auto &window = ProjectWindow::Get( project );
|
||||||
@ -94,44 +91,18 @@ bool DoPasteNothingSelected(AudacityProject &project)
|
|||||||
|
|
||||||
Track* pFirstNewTrack = NULL;
|
Track* pFirstNewTrack = NULL;
|
||||||
for (auto pClip : clipTrackRange) {
|
for (auto pClip : clipTrackRange) {
|
||||||
Track::Holder uNewTrack;
|
auto pNewTrack = pClip->PasteInto( project );
|
||||||
Track *pNewTrack;
|
bool newTrack = (pNewTrack.use_count() == 1);
|
||||||
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();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
wxASSERT(pClip);
|
wxASSERT(pClip);
|
||||||
|
|
||||||
pNewTrack->Paste(0.0, pClip);
|
|
||||||
|
|
||||||
if (!pFirstNewTrack)
|
if (!pFirstNewTrack)
|
||||||
pFirstNewTrack = pNewTrack;
|
pFirstNewTrack = pNewTrack.get();
|
||||||
|
|
||||||
pNewTrack->SetSelected(true);
|
pNewTrack->SetSelected(true);
|
||||||
if (uNewTrack)
|
if (newTrack)
|
||||||
FinishCopy(pClip, uNewTrack, tracks);
|
FinishCopy(pClip, pNewTrack, tracks);
|
||||||
else
|
else
|
||||||
Track::FinishCopy(pClip, pNewTrack);
|
Track::FinishCopy(pClip, pNewTrack.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Select some pasted samples, which is probably impossible to get right
|
// Select some pasted samples, which is probably impossible to get right
|
||||||
@ -263,10 +234,12 @@ void OnCut(const CommandContext &context)
|
|||||||
},
|
},
|
||||||
#endif
|
#endif
|
||||||
[&](Track *n) {
|
[&](Track *n) {
|
||||||
|
if (n->SupportsBasicEditing()) {
|
||||||
auto dest = n->Copy(selectedRegion.t0(),
|
auto dest = n->Copy(selectedRegion.t0(),
|
||||||
selectedRegion.t1());
|
selectedRegion.t1());
|
||||||
FinishCopy(n, dest, newClipboard);
|
FinishCopy(n, dest, newClipboard);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
// Survived possibility of exceptions. Commit changes to the clipboard now.
|
// Survived possibility of exceptions. Commit changes to the clipboard now.
|
||||||
@ -298,8 +271,8 @@ void OnCut(const CommandContext &context)
|
|||||||
fallthrough();
|
fallthrough();
|
||||||
},
|
},
|
||||||
[&](Track *n) {
|
[&](Track *n) {
|
||||||
n->Clear(selectedRegion.t0(),
|
if (n->SupportsBasicEditing())
|
||||||
selectedRegion.t1());
|
n->Clear(selectedRegion.t0(), selectedRegion.t1());
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -321,6 +294,8 @@ void OnDelete(const CommandContext &context)
|
|||||||
auto &window = ProjectWindow::Get( project );
|
auto &window = ProjectWindow::Get( project );
|
||||||
|
|
||||||
for (auto n : tracks.Any()) {
|
for (auto n : tracks.Any()) {
|
||||||
|
if (!n->SupportsBasicEditing())
|
||||||
|
continue;
|
||||||
if (n->GetSelected() || n->IsSyncLockSelected()) {
|
if (n->GetSelected() || n->IsSyncLockSelected()) {
|
||||||
n->Clear(selectedRegion.t0(), selectedRegion.t1());
|
n->Clear(selectedRegion.t0(), selectedRegion.t1());
|
||||||
}
|
}
|
||||||
@ -359,10 +334,12 @@ void OnCopy(const CommandContext &context)
|
|||||||
auto &newClipboard = *pNewClipboard;
|
auto &newClipboard = *pNewClipboard;
|
||||||
|
|
||||||
for (auto n : tracks.Selected()) {
|
for (auto n : tracks.Selected()) {
|
||||||
|
if (n->SupportsBasicEditing()) {
|
||||||
auto dest = n->Copy(selectedRegion.t0(),
|
auto dest = n->Copy(selectedRegion.t0(),
|
||||||
selectedRegion.t1());
|
selectedRegion.t1());
|
||||||
FinishCopy(n, dest, newClipboard);
|
FinishCopy(n, dest, newClipboard);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Survived possibility of exceptions. Commit changes to the clipboard now.
|
// Survived possibility of exceptions. Commit changes to the clipboard now.
|
||||||
clipboard.Assign( std::move( newClipboard ),
|
clipboard.Assign( std::move( newClipboard ),
|
||||||
@ -643,6 +620,9 @@ void OnDuplicate(const CommandContext &context)
|
|||||||
auto range = tracks.Selected();
|
auto range = tracks.Selected();
|
||||||
auto last = *range.rbegin();
|
auto last = *range.rbegin();
|
||||||
for (auto n : range) {
|
for (auto n : range) {
|
||||||
|
if (!n->SupportsBasicEditing())
|
||||||
|
continue;
|
||||||
|
|
||||||
// Make copies not for clipboard but for direct addition to the project
|
// Make copies not for clipboard but for direct addition to the project
|
||||||
auto dest = n->Copy(selectedRegion.t0(),
|
auto dest = n->Copy(selectedRegion.t0(),
|
||||||
selectedRegion.t1(), false);
|
selectedRegion.t1(), false);
|
||||||
@ -683,6 +663,7 @@ void OnSplitCut(const CommandContext &context)
|
|||||||
FinishCopy(n, dest, newClipboard);
|
FinishCopy(n, dest, newClipboard);
|
||||||
},
|
},
|
||||||
[&](Track *n) {
|
[&](Track *n) {
|
||||||
|
if (n->SupportsBasicEditing()) {
|
||||||
dest = n->Copy(selectedRegion.t0(),
|
dest = n->Copy(selectedRegion.t0(),
|
||||||
selectedRegion.t1());
|
selectedRegion.t1());
|
||||||
n->Silence(selectedRegion.t0(),
|
n->Silence(selectedRegion.t0(),
|
||||||
@ -690,6 +671,7 @@ void OnSplitCut(const CommandContext &context)
|
|||||||
if (dest)
|
if (dest)
|
||||||
FinishCopy(n, dest, newClipboard);
|
FinishCopy(n, dest, newClipboard);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
// Survived possibility of exceptions. Commit changes to the clipboard now.
|
// Survived possibility of exceptions. Commit changes to the clipboard now.
|
||||||
@ -713,6 +695,7 @@ void OnSplitDelete(const CommandContext &context)
|
|||||||
selectedRegion.t1());
|
selectedRegion.t1());
|
||||||
},
|
},
|
||||||
[&](Track *n) {
|
[&](Track *n) {
|
||||||
|
if (n->SupportsBasicEditing())
|
||||||
n->Silence(selectedRegion.t0(),
|
n->Silence(selectedRegion.t0(),
|
||||||
selectedRegion.t1());
|
selectedRegion.t1());
|
||||||
}
|
}
|
||||||
@ -1004,7 +987,7 @@ static const ReservedCommandFlag
|
|||||||
if (
|
if (
|
||||||
TimeSelectedPred( project )
|
TimeSelectedPred( project )
|
||||||
&&
|
&&
|
||||||
TracksSelectedPred( project )
|
EditableTracksSelectedPred( project )
|
||||||
)
|
)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
@ -1020,7 +1003,7 @@ BaseItemSharedPtr EditMenu()
|
|||||||
using Options = CommandManager::Options;
|
using Options = CommandManager::Options;
|
||||||
|
|
||||||
static const auto NotBusyTimeAndTracksFlags =
|
static const auto NotBusyTimeAndTracksFlags =
|
||||||
AudioIONotBusyFlag() | TimeSelectedFlag() | TracksSelectedFlag();
|
AudioIONotBusyFlag() | TimeSelectedFlag() | EditableTracksSelectedFlag();
|
||||||
|
|
||||||
// The default shortcut key for Redo is different on different platforms.
|
// The default shortcut key for Redo is different on different platforms.
|
||||||
static constexpr auto redoKey =
|
static constexpr auto redoKey =
|
||||||
@ -1065,7 +1048,7 @@ BaseItemSharedPtr EditMenu()
|
|||||||
AudioIONotBusyFlag() | CutCopyAvailableFlag() | NoAutoSelect(),
|
AudioIONotBusyFlag() | CutCopyAvailableFlag() | NoAutoSelect(),
|
||||||
wxT("Ctrl+X") ),
|
wxT("Ctrl+X") ),
|
||||||
Command( wxT("Delete"), XXO("&Delete"), FN(OnDelete),
|
Command( wxT("Delete"), XXO("&Delete"), FN(OnDelete),
|
||||||
AudioIONotBusyFlag() | TracksSelectedFlag() | TimeSelectedFlag() | NoAutoSelect(),
|
AudioIONotBusyFlag() | EditableTracksSelectedFlag() | TimeSelectedFlag() | NoAutoSelect(),
|
||||||
wxT("Ctrl+K") ),
|
wxT("Ctrl+K") ),
|
||||||
/* i18n-hint: (verb)*/
|
/* i18n-hint: (verb)*/
|
||||||
Command( wxT("Copy"), XXO("&Copy"), FN(OnCopy),
|
Command( wxT("Copy"), XXO("&Copy"), FN(OnCopy),
|
||||||
@ -1160,7 +1143,7 @@ BaseItemSharedPtr ExtraEditMenu()
|
|||||||
{
|
{
|
||||||
using Options = CommandManager::Options;
|
using Options = CommandManager::Options;
|
||||||
static const auto flags =
|
static const auto flags =
|
||||||
AudioIONotBusyFlag() | TracksSelectedFlag() | TimeSelectedFlag();
|
AudioIONotBusyFlag() | EditableTracksSelectedFlag() | TimeSelectedFlag();
|
||||||
static BaseItemSharedPtr menu{
|
static BaseItemSharedPtr menu{
|
||||||
( FinderScope{ findCommandHandler },
|
( FinderScope{ findCommandHandler },
|
||||||
Menu( wxT("Edit"), XXO("&Edit"),
|
Menu( wxT("Edit"), XXO("&Edit"),
|
||||||
@ -1184,7 +1167,7 @@ auto selectAll = []( AudacityProject &project, CommandFlag flagsRqd ){
|
|||||||
|
|
||||||
RegisteredMenuItemEnabler selectTracks{{
|
RegisteredMenuItemEnabler selectTracks{{
|
||||||
[]{ return TracksExistFlag(); },
|
[]{ return TracksExistFlag(); },
|
||||||
[]{ return TracksSelectedFlag(); },
|
[]{ return EditableTracksSelectedFlag(); },
|
||||||
canSelectAll,
|
canSelectAll,
|
||||||
selectAll
|
selectAll
|
||||||
}};
|
}};
|
||||||
|
@ -483,7 +483,7 @@ void OnSelectSyncLockSel(const CommandContext &context)
|
|||||||
auto &tracks = TrackList::Get( project );
|
auto &tracks = TrackList::Get( project );
|
||||||
|
|
||||||
bool selected = false;
|
bool selected = false;
|
||||||
for (auto t : tracks.Any()
|
for (auto t : tracks.Any() + &Track::SupportsBasicEditing
|
||||||
+ &Track::IsSyncLockSelected - &Track::IsSelected) {
|
+ &Track::IsSyncLockSelected - &Track::IsSelected) {
|
||||||
t->SetSelected(true);
|
t->SetSelected(true);
|
||||||
selected = true;
|
selected = true;
|
||||||
@ -866,7 +866,7 @@ void OnCursorTrackStart(const CommandContext &context)
|
|||||||
|
|
||||||
double kWayOverToRight = std::numeric_limits<double>::max();
|
double kWayOverToRight = std::numeric_limits<double>::max();
|
||||||
|
|
||||||
auto trackRange = tracks.Selected();
|
auto trackRange = tracks.Selected() + &Track::SupportsBasicEditing;
|
||||||
if (trackRange.empty())
|
if (trackRange.empty())
|
||||||
// This should have been prevented by command manager
|
// This should have been prevented by command manager
|
||||||
return;
|
return;
|
||||||
@ -892,7 +892,7 @@ void OnCursorTrackEnd(const CommandContext &context)
|
|||||||
|
|
||||||
double kWayOverToLeft = std::numeric_limits<double>::lowest();
|
double kWayOverToLeft = std::numeric_limits<double>::lowest();
|
||||||
|
|
||||||
auto trackRange = tracks.Selected();
|
auto trackRange = tracks.Selected() + &Track::SupportsBasicEditing;
|
||||||
if (trackRange.empty())
|
if (trackRange.empty())
|
||||||
// This should have been prevented by command manager
|
// This should have been prevented by command manager
|
||||||
return;
|
return;
|
||||||
@ -1059,7 +1059,7 @@ BaseItemSharedPtr SelectMenu()
|
|||||||
,
|
,
|
||||||
Command( wxT("SelSyncLockTracks"), XXO("In All &Sync-Locked Tracks"),
|
Command( wxT("SelSyncLockTracks"), XXO("In All &Sync-Locked Tracks"),
|
||||||
FN(OnSelectSyncLockSel),
|
FN(OnSelectSyncLockSel),
|
||||||
TracksSelectedFlag() | IsSyncLockedFlag(),
|
EditableTracksSelectedFlag() | IsSyncLockedFlag(),
|
||||||
Options{ wxT("Ctrl+Shift+Y"), XO("Select Sync-Locked") } )
|
Options{ wxT("Ctrl+Shift+Y"), XO("Select Sync-Locked") } )
|
||||||
#endif
|
#endif
|
||||||
),
|
),
|
||||||
@ -1131,7 +1131,7 @@ BaseItemSharedPtr SelectMenu()
|
|||||||
|
|
||||||
Section( "",
|
Section( "",
|
||||||
Command( wxT("ZeroCross"), XXO("At &Zero Crossings"),
|
Command( wxT("ZeroCross"), XXO("At &Zero Crossings"),
|
||||||
FN(OnZeroCrossing), TracksSelectedFlag(),
|
FN(OnZeroCrossing), EditableTracksSelectedFlag(),
|
||||||
Options{ wxT("Z"), XO("Select Zero Crossing") } )
|
Options{ wxT("Z"), XO("Select Zero Crossing") } )
|
||||||
)
|
)
|
||||||
) ) };
|
) ) };
|
||||||
@ -1216,11 +1216,11 @@ BaseItemSharedPtr CursorMenu()
|
|||||||
|
|
||||||
Command( wxT("CursTrackStart"), XXO("Track &Start"),
|
Command( wxT("CursTrackStart"), XXO("Track &Start"),
|
||||||
FN(OnCursorTrackStart),
|
FN(OnCursorTrackStart),
|
||||||
TracksSelectedFlag(),
|
EditableTracksSelectedFlag(),
|
||||||
Options{ wxT("J"), XO("Cursor to Track Start") } ),
|
Options{ wxT("J"), XO("Cursor to Track Start") } ),
|
||||||
Command( wxT("CursTrackEnd"), XXO("Track &End"),
|
Command( wxT("CursTrackEnd"), XXO("Track &End"),
|
||||||
FN(OnCursorTrackEnd),
|
FN(OnCursorTrackEnd),
|
||||||
TracksSelectedFlag(),
|
EditableTracksSelectedFlag(),
|
||||||
Options{ wxT("K"), XO("Cursor to Track End") } ),
|
Options{ wxT("K"), XO("Cursor to Track End") } ),
|
||||||
|
|
||||||
Command( wxT("CursProjectStart"), XXO("&Project Start"),
|
Command( wxT("CursProjectStart"), XXO("&Project Start"),
|
||||||
|
@ -1356,9 +1356,9 @@ BaseItemSharedPtr TracksMenu()
|
|||||||
Command( wxT("UnmuteAllTracks"), XXO("&Unmute All Tracks"),
|
Command( wxT("UnmuteAllTracks"), XXO("&Unmute All Tracks"),
|
||||||
FN(OnUnmuteAllTracks), TracksExistFlag(), wxT("Ctrl+Shift+U") ),
|
FN(OnUnmuteAllTracks), TracksExistFlag(), wxT("Ctrl+Shift+U") ),
|
||||||
Command( wxT("MuteTracks"), XXO("Mut&e Tracks"),
|
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"),
|
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"),
|
Menu( wxT("Pan"), XXO("&Pan"),
|
||||||
@ -1367,13 +1367,13 @@ BaseItemSharedPtr TracksMenu()
|
|||||||
// in the project could very easily be lost unless we
|
// in the project could very easily be lost unless we
|
||||||
// require the tracks to be selected.
|
// require the tracks to be selected.
|
||||||
Command( wxT("PanLeft"), XXO("&Left"), FN(OnPanLeft),
|
Command( wxT("PanLeft"), XXO("&Left"), FN(OnPanLeft),
|
||||||
TracksSelectedFlag(),
|
EditableTracksSelectedFlag(),
|
||||||
Options{}.LongName( XO("Pan Left") ) ),
|
Options{}.LongName( XO("Pan Left") ) ),
|
||||||
Command( wxT("PanRight"), XXO("&Right"), FN(OnPanRight),
|
Command( wxT("PanRight"), XXO("&Right"), FN(OnPanRight),
|
||||||
TracksSelectedFlag(),
|
EditableTracksSelectedFlag(),
|
||||||
Options{}.LongName( XO("Pan Right") ) ),
|
Options{}.LongName( XO("Pan Right") ) ),
|
||||||
Command( wxT("PanCenter"), XXO("&Center"), FN(OnPanCenter),
|
Command( wxT("PanCenter"), XXO("&Center"), FN(OnPanCenter),
|
||||||
TracksSelectedFlag(),
|
EditableTracksSelectedFlag(),
|
||||||
Options{}.LongName( XO("Pan Center") ) )
|
Options{}.LongName( XO("Pan Center") ) )
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
@ -1387,14 +1387,14 @@ BaseItemSharedPtr TracksMenu()
|
|||||||
{ wxT("EndToEnd"), XXO("&Align End to End") },
|
{ wxT("EndToEnd"), XXO("&Align End to End") },
|
||||||
{ wxT("Together"), XXO("Align &Together") },
|
{ wxT("Together"), XXO("Align &Together") },
|
||||||
},
|
},
|
||||||
FN(OnAlignNoSync), AudioIONotBusyFlag() | TracksSelectedFlag())
|
FN(OnAlignNoSync), AudioIONotBusyFlag() | EditableTracksSelectedFlag())
|
||||||
),
|
),
|
||||||
|
|
||||||
Section( "",
|
Section( "",
|
||||||
// Alignment commands using selection or zero
|
// Alignment commands using selection or zero
|
||||||
CommandGroup(wxT("Align"),
|
CommandGroup(wxT("Align"),
|
||||||
alignLabels(),
|
alignLabels(),
|
||||||
FN(OnAlign), AudioIONotBusyFlag() | TracksSelectedFlag())
|
FN(OnAlign), AudioIONotBusyFlag() | EditableTracksSelectedFlag())
|
||||||
),
|
),
|
||||||
|
|
||||||
Section( "",
|
Section( "",
|
||||||
@ -1411,7 +1411,7 @@ BaseItemSharedPtr TracksMenu()
|
|||||||
// Do we need this sub-menu at all?
|
// Do we need this sub-menu at all?
|
||||||
Menu( wxT("MoveSelectionAndTracks"), XO("Move Sele&ction and Tracks"), {
|
Menu( wxT("MoveSelectionAndTracks"), XO("Move Sele&ction and Tracks"), {
|
||||||
CommandGroup(wxT("AlignMove"), alignLabels(),
|
CommandGroup(wxT("AlignMove"), alignLabels(),
|
||||||
FN(OnAlignMoveSel), AudioIONotBusyFlag() | TracksSelectedFlag()),
|
FN(OnAlignMoveSel), AudioIONotBusyFlag() | EditableTracksSelectedFlag()),
|
||||||
} ),
|
} ),
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user