/* * Undo.h * ------ * Purpose: Editor undo buffer functionality. * Notes : (currently none) * Authors: Olivier Lapicque * OpenMPT Devs * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. */ #pragma once #include "openmpt/all/BuildSettings.hpp" #include "../soundlib/ModChannel.h" #include "../soundlib/modcommand.h" OPENMPT_NAMESPACE_BEGIN class CModDoc; struct ModSample; #define MAX_UNDO_LEVEL 100000 // 100,000 undo steps for each undo type! ///////////////////////////////////////////////////////////////////////////////////////// // Pattern Undo class CPatternUndo { protected: static constexpr auto DELETE_PATTERN = PATTERNINDEX_INVALID; struct UndoInfo { std::vector channelInfo; // Optional old channel information (pan / volume / etc.) std::vector content; // Rescued pattern content const char *description; // Name of this undo action ROWINDEX numPatternRows; // Original number of pattern rows (in case of resize, DELETE_PATTERN in case of deletion) ROWINDEX firstRow, numRows; PATTERNINDEX pattern; CHANNELINDEX firstChannel, numChannels; bool linkToPrevious; // This undo information is linked with the previous undo information bool OnlyChannelSettings() const noexcept { return !channelInfo.empty() && numRows < 1 && !linkToPrevious; } }; using undobuf_t = std::vector; undobuf_t UndoBuffer; undobuf_t RedoBuffer; CModDoc &modDoc; // Pattern undo helper functions PATTERNINDEX Undo(undobuf_t &fromBuf, undobuf_t &toBuf, bool linkedFromPrevious); bool PrepareBuffer(undobuf_t &buffer, PATTERNINDEX pattern, CHANNELINDEX firstChn, ROWINDEX firstRow, CHANNELINDEX numChns, ROWINDEX numRows, const char *description, bool linkToPrevious, bool storeChannelInfo) const; static CString GetName(const undobuf_t &buffer); static void RearrangePatterns(undobuf_t &buffer, const std::vector &newIndex); public: // Removes all undo steps from the buffer. void ClearUndo(); // Adds a new action to the undo buffer. bool PrepareUndo(PATTERNINDEX pattern, CHANNELINDEX firstChn, ROWINDEX firstRow, CHANNELINDEX numChns, ROWINDEX numRows, const char *description, bool linkToPrevious = false, bool storeChannelInfo = false); // Adds a new action only affecting channel data to the undo buffer. bool PrepareChannelUndo(CHANNELINDEX firstChn, CHANNELINDEX numChns, const char *description); // Undoes the most recent action. PATTERNINDEX Undo(); // Redoes the most recent action. PATTERNINDEX Redo(); // Returns true if any actions can currently be undone. bool CanUndo() const { return !UndoBuffer.empty(); } // Returns true if any actions can currently be redone. bool CanRedo() const { return !RedoBuffer.empty(); } // Returns true if a channel-specific action (no pattern data) can currently be undone. bool CanUndoChannelSettings() const { return !UndoBuffer.empty() && UndoBuffer.back().OnlyChannelSettings(); } // Returns true if a channel-specific action (no pattern data) actions can currently be redone. bool CanRedoChannelSettings() const { return !RedoBuffer.empty() && RedoBuffer.back().OnlyChannelSettings(); } // Remove the latest added undo step from the undo buffer void RemoveLastUndoStep(); // Get name of next undo item CString GetUndoName() const { return GetName(UndoBuffer); } // Get name of next redo item CString GetRedoName() const { return GetName(RedoBuffer); } // Adjust undo buffers for rearranged patterns void RearrangePatterns(const std::vector &newIndex); CPatternUndo(CModDoc &parent) : modDoc(parent) { } }; ///////////////////////////////////////////////////////////////////////////////////////// // Sample Undo // We will differentiate between different types of undo actions so that we don't have to copy the whole sample everytime. enum sampleUndoTypes { sundo_none, // no changes to sample itself, e.g. loop point update sundo_update, // silence, amplify, normalize, dc offset - update complete sample section sundo_delete, // delete part of the sample sundo_invert, // invert sample phase, apply again to undo sundo_reverse, // reverse sample, ditto sundo_unsign, // unsign sample, ditto sundo_insert, // insert data, delete inserted data to undo sundo_replace, // replace complete sample (16->8Bit, up/downsample, downmix to mono, pitch shifting / time stretching, trimming, pasting) }; class CSampleUndo { protected: struct UndoInfo { ModSample OldSample; mpt::charbuf oldName; void *samplePtr = nullptr; const char *description = nullptr; SmpLength changeStart = 0, changeEnd = 0; sampleUndoTypes changeType = sundo_none; }; using undobuf_t = std::vector>; undobuf_t UndoBuffer; undobuf_t RedoBuffer; CModDoc &modDoc; // Sample undo helper functions void ClearUndo(undobuf_t &buffer, const SAMPLEINDEX smp); void DeleteStep(undobuf_t &buffer, const SAMPLEINDEX smp, const size_t step); bool SampleBufferExists(const undobuf_t &buffer, const SAMPLEINDEX smp) const; void RestrictBufferSize(undobuf_t &buffer, size_t &capacity); size_t GetBufferCapacity(const undobuf_t &buffer) const; void RearrangeSamples(undobuf_t &buffer, const std::vector &newIndex); bool PrepareBuffer(undobuf_t &buffer, const SAMPLEINDEX smp, sampleUndoTypes changeType, const char *description, SmpLength changeStart, SmpLength changeEnd); bool Undo(undobuf_t &fromBuf, undobuf_t &toBuf, const SAMPLEINDEX smp); public: // Sample undo functions void ClearUndo(); void ClearUndo(const SAMPLEINDEX smp) { ClearUndo(UndoBuffer, smp); ClearUndo(RedoBuffer, smp); } bool PrepareUndo(const SAMPLEINDEX smp, sampleUndoTypes changeType, const char *description, SmpLength changeStart = 0, SmpLength changeEnd = 0); bool Undo(const SAMPLEINDEX smp); bool Redo(const SAMPLEINDEX smp); bool CanUndo(const SAMPLEINDEX smp) const { return SampleBufferExists(UndoBuffer, smp) && !UndoBuffer[smp - 1].empty(); } bool CanRedo(const SAMPLEINDEX smp) const { return SampleBufferExists(RedoBuffer, smp) && !RedoBuffer[smp - 1].empty(); } void RemoveLastUndoStep(const SAMPLEINDEX smp); const char *GetUndoName(const SAMPLEINDEX smp) const; const char *GetRedoName(const SAMPLEINDEX smp) const; void RestrictBufferSize(); void RearrangeSamples(const std::vector &newIndex) { RearrangeSamples(UndoBuffer, newIndex); RearrangeSamples(RedoBuffer, newIndex); } CSampleUndo(CModDoc &parent) : modDoc(parent) { } ~CSampleUndo() { ClearUndo(); }; }; ///////////////////////////////////////////////////////////////////////////////////////// // Instrument Undo class CInstrumentUndo { protected: struct UndoInfo { ModInstrument instr; const char *description = nullptr; EnvelopeType editedEnvelope = ENV_MAXTYPES; }; using undobuf_t = std::vector>; undobuf_t UndoBuffer; undobuf_t RedoBuffer; CModDoc &modDoc; // Instrument undo helper functions void ClearUndo(undobuf_t &buffer, const INSTRUMENTINDEX ins); void DeleteStep(undobuf_t &buffer, const INSTRUMENTINDEX ins, const size_t step); bool InstrumentBufferExists(const undobuf_t &buffer, const INSTRUMENTINDEX ins) const; void RearrangeInstruments(undobuf_t &buffer, const std::vector &newIndex); void RearrangeSamples(undobuf_t &buffer, const INSTRUMENTINDEX ins, std::vector &newIndex); bool PrepareBuffer(undobuf_t &buffer, const INSTRUMENTINDEX ins, const char *description, EnvelopeType envType); bool Undo(undobuf_t &fromBuf, undobuf_t &toBuf, const INSTRUMENTINDEX ins); public: // Instrument undo functions void ClearUndo(); void ClearUndo(const INSTRUMENTINDEX ins) { ClearUndo(UndoBuffer, ins); ClearUndo(RedoBuffer, ins); } bool PrepareUndo(const INSTRUMENTINDEX ins, const char *description, EnvelopeType envType = ENV_MAXTYPES); bool Undo(const INSTRUMENTINDEX ins); bool Redo(const INSTRUMENTINDEX ins); bool CanUndo(const INSTRUMENTINDEX ins) const { return InstrumentBufferExists(UndoBuffer, ins) && !UndoBuffer[ins - 1].empty(); } bool CanRedo(const INSTRUMENTINDEX ins) const { return InstrumentBufferExists(RedoBuffer, ins) && !RedoBuffer[ins - 1].empty(); } void RemoveLastUndoStep(const INSTRUMENTINDEX ins); const char *GetUndoName(const INSTRUMENTINDEX ins) const; const char *GetRedoName(const INSTRUMENTINDEX ins) const; void RearrangeInstruments(const std::vector &newIndex) { RearrangeInstruments(UndoBuffer, newIndex); RearrangeInstruments(RedoBuffer, newIndex); } void RearrangeSamples(const INSTRUMENTINDEX ins, std::vector &newIndex) { RearrangeSamples(UndoBuffer, ins, newIndex); RearrangeSamples(RedoBuffer, ins, newIndex); } CInstrumentUndo(CModDoc &parent) : modDoc(parent) { } ~CInstrumentUndo() { ClearUndo(); }; }; OPENMPT_NAMESPACE_END