mirror of
https://github.com/cookiengineer/audacity
synced 2025-05-02 00:29:41 +02:00
Bug 1823: Better interaction of undo and record...
... If you record or append-record, and do things during it that affect the undo stack, and then complete recording -- then the last undo item will undo the recording completely, and the other changes will belong to earlier undo items that contain no part of the new recording. Things that affect the undo stack during recording can include new undo items such as movement of pan and gain sliders or adding and editing of labels with Ctrl+M or Command+. But such things also include certain changes of view that do not create new undo items but rather update the most recent undo item to include that change. Examples are change of mute or solo, resizing of tracks, changes of selection, zooming (horizontally or vertically), switch among wave track view types, etc. Fixing this was a rather involved project. This ought to be tested thoroughly to be sure there are no other odd surprises in the behavior of undo.
This commit is contained in:
commit
1e8aba968d
@ -2782,6 +2782,10 @@ void AudioIO::StopStream()
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
AudacityProject *p = GetActiveProject();
|
||||
ControlToolBar *bar = p->GetControlToolBar();
|
||||
bar->CommitRecording();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -792,15 +792,6 @@ std::shared_ptr<TrackList> TrackList::Create()
|
||||
return result;
|
||||
}
|
||||
|
||||
TrackList& TrackList::operator= (const TrackList &that)
|
||||
{
|
||||
if (this != &that) {
|
||||
this->Clear();
|
||||
DoAssign(that);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
TrackList &TrackList::operator= (TrackList &&that)
|
||||
{
|
||||
if (this != &that) {
|
||||
@ -810,24 +801,6 @@ TrackList &TrackList::operator= (TrackList &&that)
|
||||
return *this;
|
||||
}
|
||||
|
||||
void TrackList::DoAssign(const TrackList &that)
|
||||
{
|
||||
auto copyLOT = [](
|
||||
ListOfTracks &dst, const std::weak_ptr< TrackList > &self,
|
||||
const ListOfTracks &src )
|
||||
{
|
||||
for (const auto &ptr : src)
|
||||
dst.push_back(
|
||||
ListOfTracks::value_type{ ptr->Duplicate().release() } );
|
||||
for (auto it = dst.begin(), last = dst.end(); it != last; ++it)
|
||||
(*it)->SetOwner(self, it);
|
||||
};
|
||||
copyLOT( *this, mSelf, that );
|
||||
copyLOT( this->mPendingAdditions, mSelf, that.mPendingAdditions );
|
||||
copyLOT( this->mPendingUpdates, mSelf, that.mPendingUpdates );
|
||||
mUpdaters = that.mUpdaters;
|
||||
}
|
||||
|
||||
void TrackList::Swap(TrackList &that)
|
||||
{
|
||||
auto SwapLOTs = [](
|
||||
|
15
src/Track.h
15
src/Track.h
@ -659,8 +659,13 @@ class TrackList final : public wxEvtHandler, public ListOfTracks
|
||||
// Create an empty TrackList
|
||||
TrackList();
|
||||
|
||||
// Disallow copy
|
||||
TrackList(const TrackList &that) = delete;
|
||||
TrackList(TrackList &&that) = delete;
|
||||
TrackList &operator= (const TrackList&) = delete;
|
||||
|
||||
// Allow move
|
||||
TrackList(TrackList &&that) : TrackList() { Swap(that); }
|
||||
TrackList& operator= (TrackList&&);
|
||||
|
||||
void clear() = delete;
|
||||
|
||||
@ -668,12 +673,6 @@ class TrackList final : public wxEvtHandler, public ListOfTracks
|
||||
// Create an empty TrackList
|
||||
static std::shared_ptr<TrackList> Create();
|
||||
|
||||
// Allow copy -- a deep copy that duplicates all tracks
|
||||
TrackList &operator= (const TrackList &that);
|
||||
|
||||
// Allow move
|
||||
TrackList& operator= (TrackList&&);
|
||||
|
||||
// Move is defined in terms of Swap
|
||||
void Swap(TrackList &that);
|
||||
|
||||
@ -831,8 +830,6 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
void DoAssign(const TrackList &that);
|
||||
|
||||
void RecalcPositions(TrackNodePointer node);
|
||||
void PermutationEvent();
|
||||
void DeletionEvent();
|
||||
|
@ -163,6 +163,33 @@ void WaveTrack::Init(const WaveTrack &orig)
|
||||
mDisplayLocationsCache.clear();
|
||||
}
|
||||
|
||||
void WaveTrack::Reinit(const WaveTrack &orig)
|
||||
{
|
||||
Init(orig);
|
||||
|
||||
{
|
||||
auto &settings = orig.mpSpectrumSettings;
|
||||
if (settings)
|
||||
mpSpectrumSettings = std::make_unique<SpectrogramSettings>(*settings);
|
||||
else
|
||||
mpSpectrumSettings.reset();
|
||||
}
|
||||
|
||||
{
|
||||
auto &settings = orig.mpWaveformSettings;
|
||||
if (settings)
|
||||
mpWaveformSettings = std::make_unique<WaveformSettings>(*settings);
|
||||
else
|
||||
mpWaveformSettings.reset();
|
||||
}
|
||||
|
||||
this->SetOffset(orig.GetOffset());
|
||||
|
||||
#ifdef EXPERIMENTAL_OUTPUT_DISPLAY
|
||||
// To do: mYv, mHeightV, mPerY, mVirtualStereo
|
||||
#endif
|
||||
}
|
||||
|
||||
void WaveTrack::Merge(const Track &orig)
|
||||
{
|
||||
if (orig.GetKind() == Wave)
|
||||
|
@ -74,6 +74,12 @@ class AUDACITY_DLL_API WaveTrack final : public PlayableTrack {
|
||||
|
||||
void Init(const WaveTrack &orig);
|
||||
|
||||
public:
|
||||
// overwrite data excluding the sample sequence but including display
|
||||
// settings
|
||||
void Reinit(const WaveTrack &orig);
|
||||
|
||||
private:
|
||||
Track::Holder Duplicate() const override;
|
||||
|
||||
friend class TrackFactory;
|
||||
|
@ -958,24 +958,11 @@ void ControlToolBar::OnRecord(wxCommandEvent &evt)
|
||||
shifted = !shifted;
|
||||
|
||||
TrackList *trackList = p->GetTracks();
|
||||
auto pTracksCopy = TrackList::Create();
|
||||
auto &tracksCopy = *pTracksCopy;
|
||||
bool tracksCopied = false;
|
||||
|
||||
WaveTrackArray recordingTracks;
|
||||
|
||||
auto cleanup = finally( [&] {
|
||||
if (!success) {
|
||||
if (tracksCopied)
|
||||
// Restore the tracks to remove any inserted silence
|
||||
*trackList = std::move(tracksCopy);
|
||||
|
||||
if ( ! shifted ) {
|
||||
// msmeyer: Delete recently added tracks if opening stream fails
|
||||
for ( auto track : recordingTracks )
|
||||
trackList->Remove(track.get());
|
||||
}
|
||||
|
||||
SetPlay(false);
|
||||
SetStop(false);
|
||||
SetRecord(false);
|
||||
@ -1022,13 +1009,13 @@ void ControlToolBar::OnRecord(wxCommandEvent &evt)
|
||||
#ifdef EXPERIMENTAL_MIDI_OUT
|
||||
midiTracks = trackList->GetNoteTrackArray(false);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
else {
|
||||
playbackTracks = WaveTrackConstArray();
|
||||
#ifdef EXPERIMENTAL_MIDI_OUT
|
||||
midiTracks = NoteTrackArray();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
// If SHIFT key was down, the user wants append to tracks
|
||||
int recordingChannels = 0;
|
||||
@ -1076,14 +1063,23 @@ void ControlToolBar::OnRecord(wxCommandEvent &evt)
|
||||
t1 = wt->GetEndTime();
|
||||
// less than or equal, not just less than, to ensure a clip boundary.
|
||||
// when append recording.
|
||||
|
||||
// A function that copies all the non-sample data between
|
||||
// wave tracks; in case the track recorded to changes scale
|
||||
// type (for instance), during the recording.
|
||||
auto updater = [](Track &d, const Track &s){
|
||||
auto &dst = static_cast<WaveTrack&>(d);
|
||||
auto &src = static_cast<const WaveTrack&>(s);
|
||||
dst.Reinit(src);
|
||||
};
|
||||
|
||||
// Get a copy of the track to be appended, to be pushed into
|
||||
// undo history only later.
|
||||
auto pending = std::static_pointer_cast<WaveTrack>(
|
||||
p->GetTracks()->RegisterPendingChangedTrack(
|
||||
updater, wt.get() ) );
|
||||
|
||||
if (t1 <= t0) {
|
||||
if (!tracksCopied) {
|
||||
// Duplicate all tracks before modifying any of them.
|
||||
// The duplicates are used to restore state in case
|
||||
// of failure.
|
||||
tracksCopied = true;
|
||||
tracksCopy = *trackList;
|
||||
}
|
||||
|
||||
// Pad the recording track with silence, up to the
|
||||
// maximum time.
|
||||
@ -1091,10 +1087,10 @@ void ControlToolBar::OnRecord(wxCommandEvent &evt)
|
||||
newTrack->SetWaveColorIndex( wt->GetWaveColorIndex() );
|
||||
newTrack->InsertSilence(0.0, t0 - t1);
|
||||
newTrack->Flush();
|
||||
wt->Clear(t1, t0);
|
||||
wt->Paste(t1, newTrack.get());
|
||||
pending->Clear(t1, t0);
|
||||
pending->Paste(t1, newTrack.get());
|
||||
}
|
||||
recordingTracks.push_back(wt);
|
||||
recordingTracks.push_back(pending);
|
||||
// Don't record more channels than configured recording pref.
|
||||
if( (int)recordingTracks.size() >= recordingChannels ){
|
||||
break;
|
||||
@ -1130,7 +1126,9 @@ void ControlToolBar::OnRecord(wxCommandEvent &evt)
|
||||
wxString baseTrackName = recordingNameCustom? defaultRecordingTrackName : defaultTrackName;
|
||||
|
||||
for (int c = 0; c < recordingChannels; c++) {
|
||||
auto newTrack = p->GetTrackFactory()->NewWaveTrack();
|
||||
std::shared_ptr<WaveTrack> newTrack{
|
||||
p->GetTrackFactory()->NewWaveTrack().release()
|
||||
};
|
||||
|
||||
newTrack->SetOffset(t0);
|
||||
wxString nameSuffix = wxString(wxT(""));
|
||||
@ -1182,11 +1180,8 @@ void ControlToolBar::OnRecord(wxCommandEvent &evt)
|
||||
newTrack->SetChannel( Track::MonoChannel );
|
||||
}
|
||||
|
||||
// Let the list hold the track, and keep a pointer to it
|
||||
recordingTracks.push_back(
|
||||
Track::Pointer<WaveTrack>(
|
||||
trackList->Add(
|
||||
std::move(newTrack))));
|
||||
p->GetTracks()->RegisterPendingNewTrack( newTrack );
|
||||
recordingTracks.push_back( newTrack );
|
||||
}
|
||||
}
|
||||
|
||||
@ -1212,6 +1207,8 @@ void ControlToolBar::OnRecord(wxCommandEvent &evt)
|
||||
StartScrollingIfPreferred();
|
||||
}
|
||||
else {
|
||||
CancelRecording();
|
||||
|
||||
// Show error message if stream could not be opened
|
||||
ShowErrorDialog(this, _("Error"),
|
||||
_("Error opening sound device.\nTry changing the audio host, recording device and the project sample rate."),
|
||||
@ -1453,3 +1450,15 @@ void ControlToolBar::StopScrolling()
|
||||
project->GetPlaybackScroller().Activate
|
||||
(AudacityProject::PlaybackScroller::Mode::Off);
|
||||
}
|
||||
|
||||
void ControlToolBar::CommitRecording()
|
||||
{
|
||||
const auto project = GetActiveProject();
|
||||
project->GetTracks()->ApplyPendingTracks();
|
||||
}
|
||||
|
||||
void ControlToolBar::CancelRecording()
|
||||
{
|
||||
const auto project = GetActiveProject();
|
||||
project->GetTracks()->ClearPendingTracks();
|
||||
}
|
||||
|
@ -112,6 +112,12 @@ class ControlToolBar final : public ToolBar {
|
||||
void StartScrolling();
|
||||
void StopScrolling();
|
||||
|
||||
// Commit the addition of temporary recording tracks into the project
|
||||
void CommitRecording();
|
||||
|
||||
// Cancel the addition of temporary recording tracks into the project
|
||||
void CancelRecording();
|
||||
|
||||
private:
|
||||
|
||||
AButton *MakeButton(teBmps eEnabledUp, teBmps eEnabledDown, teBmps eDisabled,
|
||||
|
Loading…
x
Reference in New Issue
Block a user