1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-05-02 08:39:46 +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:
Paul Licameli 2018-01-14 19:33:57 -05:00
commit 1e8aba968d
7 changed files with 89 additions and 67 deletions

View File

@ -2782,6 +2782,10 @@ void AudioIO::StopStream()
}
} );
}
AudacityProject *p = GetActiveProject();
ControlToolBar *bar = p->GetControlToolBar();
bar->CommitRecording();
}
}

View File

@ -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 = [](

View File

@ -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();

View File

@ -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)

View File

@ -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;

View File

@ -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();
}

View File

@ -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,