/* ============================================================================== This file is part of the JUCE library - "Jules' Utility Class Extensions" Copyright 2004-11 by Raw Material Software Ltd. ------------------------------------------------------------------------------ JUCE can be redistributed and/or modified under the terms of the GNU General Public License (Version 2), as published by the Free Software Foundation. A copy of the license is included in the JUCE distribution, or can be found online at www.gnu.org/licenses. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. ------------------------------------------------------------------------------ To release a closed-source product which uses JUCE, commercial licenses are available: visit www.rawmaterialsoftware.com/juce for more information. ============================================================================== */ BEGIN_JUCE_NAMESPACE //============================================================================== UndoManager::UndoManager (const int maxNumberOfUnitsToKeep, const int minimumTransactions) : totalUnitsStored (0), nextIndex (0), newTransaction (true), reentrancyCheck (false) { setMaxNumberOfStoredUnits (maxNumberOfUnitsToKeep, minimumTransactions); } UndoManager::~UndoManager() { clearUndoHistory(); } //============================================================================== void UndoManager::clearUndoHistory() { transactions.clear(); transactionNames.clear(); totalUnitsStored = 0; nextIndex = 0; sendChangeMessage(); } int UndoManager::getNumberOfUnitsTakenUpByStoredCommands() const { return totalUnitsStored; } void UndoManager::setMaxNumberOfStoredUnits (const int maxNumberOfUnitsToKeep, const int minimumTransactions) { maxNumUnitsToKeep = jmax (1, maxNumberOfUnitsToKeep); minimumTransactionsToKeep = jmax (1, minimumTransactions); } //============================================================================== bool UndoManager::perform (UndoableAction* const command_, const String& actionName) { if (command_ != nullptr) { ScopedPointer command (command_); if (actionName.isNotEmpty()) currentTransactionName = actionName; if (reentrancyCheck) { jassertfalse; // don't call perform() recursively from the UndoableAction::perform() or // undo() methods, or else these actions won't actually get done. return false; } else if (command->perform()) { OwnedArray* commandSet = transactions [nextIndex - 1]; if (commandSet != nullptr && ! newTransaction) { UndoableAction* lastAction = commandSet->getLast(); if (lastAction != nullptr) { UndoableAction* coalescedAction = lastAction->createCoalescedAction (command); if (coalescedAction != nullptr) { command = coalescedAction; totalUnitsStored -= lastAction->getSizeInUnits(); commandSet->removeLast(); } } } else { commandSet = new OwnedArray(); transactions.insert (nextIndex, commandSet); transactionNames.insert (nextIndex, currentTransactionName); ++nextIndex; } totalUnitsStored += command->getSizeInUnits(); commandSet->add (command.release()); newTransaction = false; while (nextIndex < transactions.size()) { const OwnedArray * const lastSet = transactions.getLast(); for (int i = lastSet->size(); --i >= 0;) totalUnitsStored -= lastSet->getUnchecked (i)->getSizeInUnits(); transactions.removeLast(); transactionNames.remove (transactionNames.size() - 1); } while (nextIndex > 0 && totalUnitsStored > maxNumUnitsToKeep && transactions.size() > minimumTransactionsToKeep) { const OwnedArray * const firstSet = transactions.getFirst(); for (int i = firstSet->size(); --i >= 0;) totalUnitsStored -= firstSet->getUnchecked (i)->getSizeInUnits(); jassert (totalUnitsStored >= 0); // something fishy going on if this fails! transactions.remove (0); transactionNames.remove (0); --nextIndex; } sendChangeMessage(); return true; } } return false; } void UndoManager::beginNewTransaction (const String& actionName) { newTransaction = true; currentTransactionName = actionName; } void UndoManager::setCurrentTransactionName (const String& newName) { currentTransactionName = newName; } //============================================================================== bool UndoManager::canUndo() const { return nextIndex > 0; } bool UndoManager::canRedo() const { return nextIndex < transactions.size(); } String UndoManager::getUndoDescription() const { return transactionNames [nextIndex - 1]; } String UndoManager::getRedoDescription() const { return transactionNames [nextIndex]; } bool UndoManager::undo() { const OwnedArray* const commandSet = transactions [nextIndex - 1]; if (commandSet == nullptr) return false; bool failed = false; { const ScopedValueSetter setter (reentrancyCheck, true); for (int i = commandSet->size(); --i >= 0;) { if (! commandSet->getUnchecked(i)->undo()) { jassertfalse; failed = true; break; } } } if (failed) clearUndoHistory(); else --nextIndex; beginNewTransaction(); sendChangeMessage(); return true; } bool UndoManager::redo() { const OwnedArray* const commandSet = transactions [nextIndex]; if (commandSet == nullptr) return false; bool failed = false; { const ScopedValueSetter setter (reentrancyCheck, true); for (int i = 0; i < commandSet->size(); ++i) { if (! commandSet->getUnchecked(i)->perform()) { jassertfalse; failed = true; break; } } } if (failed) clearUndoHistory(); else ++nextIndex; beginNewTransaction(); sendChangeMessage(); return true; } bool UndoManager::undoCurrentTransactionOnly() { return newTransaction ? false : undo(); } void UndoManager::getActionsInCurrentTransaction (Array & actionsFound) const { const OwnedArray * const commandSet = transactions [nextIndex - 1]; if (commandSet != nullptr && ! newTransaction) { for (int i = 0; i < commandSet->size(); ++i) actionsFound.add (commandSet->getUnchecked(i)); } } int UndoManager::getNumActionsInCurrentTransaction() const { const OwnedArray * const commandSet = transactions [nextIndex - 1]; if (commandSet != nullptr && ! newTransaction) return commandSet->size(); return 0; } END_JUCE_NAMESPACE