Audio plugin host https://kx.studio/carla
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

353 lines
9.3KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2017 - ROLI Ltd.
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 5 End-User License
  8. Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
  9. 27th April 2017).
  10. End User License Agreement: www.juce.com/juce-5-licence
  11. Privacy Policy: www.juce.com/juce-5-privacy-policy
  12. Or: You may also use this code under the terms of the GPL v3 (see
  13. www.gnu.org/licenses).
  14. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  15. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  16. DISCLAIMED.
  17. ==============================================================================
  18. */
  19. namespace juce
  20. {
  21. struct UndoManager::ActionSet
  22. {
  23. ActionSet (const String& transactionName)
  24. : name (transactionName),
  25. time (Time::getCurrentTime())
  26. {}
  27. bool perform() const
  28. {
  29. for (int i = 0; i < actions.size(); ++i)
  30. if (! actions.getUnchecked(i)->perform())
  31. return false;
  32. return true;
  33. }
  34. bool undo() const
  35. {
  36. for (int i = actions.size(); --i >= 0;)
  37. if (! actions.getUnchecked(i)->undo())
  38. return false;
  39. return true;
  40. }
  41. int getTotalSize() const
  42. {
  43. int total = 0;
  44. for (int i = actions.size(); --i >= 0;)
  45. total += actions.getUnchecked(i)->getSizeInUnits();
  46. return total;
  47. }
  48. OwnedArray<UndoableAction> actions;
  49. String name;
  50. Time time;
  51. };
  52. //==============================================================================
  53. UndoManager::UndoManager (const int maxNumberOfUnitsToKeep,
  54. const int minimumTransactions)
  55. : totalUnitsStored (0),
  56. nextIndex (0),
  57. newTransaction (true),
  58. reentrancyCheck (false)
  59. {
  60. setMaxNumberOfStoredUnits (maxNumberOfUnitsToKeep,
  61. minimumTransactions);
  62. }
  63. UndoManager::~UndoManager()
  64. {
  65. }
  66. //==============================================================================
  67. void UndoManager::clearUndoHistory()
  68. {
  69. transactions.clear();
  70. totalUnitsStored = 0;
  71. nextIndex = 0;
  72. sendChangeMessage();
  73. }
  74. int UndoManager::getNumberOfUnitsTakenUpByStoredCommands() const
  75. {
  76. return totalUnitsStored;
  77. }
  78. void UndoManager::setMaxNumberOfStoredUnits (const int maxNumberOfUnitsToKeep,
  79. const int minimumTransactions)
  80. {
  81. maxNumUnitsToKeep = jmax (1, maxNumberOfUnitsToKeep);
  82. minimumTransactionsToKeep = jmax (1, minimumTransactions);
  83. }
  84. //==============================================================================
  85. bool UndoManager::perform (UndoableAction* const newAction, const String& actionName)
  86. {
  87. if (perform (newAction))
  88. {
  89. if (actionName.isNotEmpty())
  90. setCurrentTransactionName (actionName);
  91. return true;
  92. }
  93. return false;
  94. }
  95. bool UndoManager::perform (UndoableAction* const newAction)
  96. {
  97. if (newAction != nullptr)
  98. {
  99. ScopedPointer<UndoableAction> action (newAction);
  100. if (reentrancyCheck)
  101. {
  102. jassertfalse; // don't call perform() recursively from the UndoableAction::perform()
  103. // or undo() methods, or else these actions will be discarded!
  104. return false;
  105. }
  106. if (action->perform())
  107. {
  108. ActionSet* actionSet = getCurrentSet();
  109. if (actionSet != nullptr && ! newTransaction)
  110. {
  111. if (UndoableAction* const lastAction = actionSet->actions.getLast())
  112. {
  113. if (UndoableAction* const coalescedAction = lastAction->createCoalescedAction (action))
  114. {
  115. action = coalescedAction;
  116. totalUnitsStored -= lastAction->getSizeInUnits();
  117. actionSet->actions.removeLast();
  118. }
  119. }
  120. }
  121. else
  122. {
  123. actionSet = new ActionSet (newTransactionName);
  124. transactions.insert (nextIndex, actionSet);
  125. ++nextIndex;
  126. }
  127. totalUnitsStored += action->getSizeInUnits();
  128. actionSet->actions.add (action.release());
  129. newTransaction = false;
  130. moveFutureTransactionsToStash();
  131. dropOldTransactionsIfTooLarge();
  132. sendChangeMessage();
  133. return true;
  134. }
  135. }
  136. return false;
  137. }
  138. void UndoManager::moveFutureTransactionsToStash()
  139. {
  140. if (nextIndex < transactions.size())
  141. {
  142. stashedFutureTransactions.clear();
  143. while (nextIndex < transactions.size())
  144. {
  145. ActionSet* removed = transactions.removeAndReturn (nextIndex);
  146. stashedFutureTransactions.add (removed);
  147. totalUnitsStored -= removed->getTotalSize();
  148. }
  149. }
  150. }
  151. void UndoManager::restoreStashedFutureTransactions()
  152. {
  153. while (nextIndex < transactions.size())
  154. {
  155. totalUnitsStored -= transactions.getUnchecked (nextIndex)->getTotalSize();
  156. transactions.remove (nextIndex);
  157. }
  158. for (int i = 0; i < stashedFutureTransactions.size(); ++i)
  159. {
  160. ActionSet* action = stashedFutureTransactions.removeAndReturn (i);
  161. totalUnitsStored += action->getTotalSize();
  162. transactions.add (action);
  163. }
  164. stashedFutureTransactions.clearQuick (false);
  165. }
  166. void UndoManager::dropOldTransactionsIfTooLarge()
  167. {
  168. while (nextIndex > 0
  169. && totalUnitsStored > maxNumUnitsToKeep
  170. && transactions.size() > minimumTransactionsToKeep)
  171. {
  172. totalUnitsStored -= transactions.getFirst()->getTotalSize();
  173. transactions.remove (0);
  174. --nextIndex;
  175. // if this fails, then some actions may not be returning
  176. // consistent results from their getSizeInUnits() method
  177. jassert (totalUnitsStored >= 0);
  178. }
  179. }
  180. void UndoManager::beginNewTransaction() noexcept
  181. {
  182. beginNewTransaction (String());
  183. }
  184. void UndoManager::beginNewTransaction (const String& actionName) noexcept
  185. {
  186. newTransaction = true;
  187. newTransactionName = actionName;
  188. }
  189. void UndoManager::setCurrentTransactionName (const String& newName) noexcept
  190. {
  191. if (newTransaction)
  192. newTransactionName = newName;
  193. else if (ActionSet* action = getCurrentSet())
  194. action->name = newName;
  195. }
  196. String UndoManager::getCurrentTransactionName() const noexcept
  197. {
  198. if (ActionSet* action = getCurrentSet())
  199. return action->name;
  200. return newTransactionName;
  201. }
  202. //==============================================================================
  203. UndoManager::ActionSet* UndoManager::getCurrentSet() const noexcept { return transactions [nextIndex - 1]; }
  204. UndoManager::ActionSet* UndoManager::getNextSet() const noexcept { return transactions [nextIndex]; }
  205. bool UndoManager::canUndo() const noexcept { return getCurrentSet() != nullptr; }
  206. bool UndoManager::canRedo() const noexcept { return getNextSet() != nullptr; }
  207. bool UndoManager::undo()
  208. {
  209. if (const ActionSet* const s = getCurrentSet())
  210. {
  211. const ScopedValueSetter<bool> setter (reentrancyCheck, true);
  212. if (s->undo())
  213. --nextIndex;
  214. else
  215. clearUndoHistory();
  216. beginNewTransaction();
  217. sendChangeMessage();
  218. return true;
  219. }
  220. return false;
  221. }
  222. bool UndoManager::redo()
  223. {
  224. if (const ActionSet* const s = getNextSet())
  225. {
  226. const ScopedValueSetter<bool> setter (reentrancyCheck, true);
  227. if (s->perform())
  228. ++nextIndex;
  229. else
  230. clearUndoHistory();
  231. beginNewTransaction();
  232. sendChangeMessage();
  233. return true;
  234. }
  235. return false;
  236. }
  237. String UndoManager::getUndoDescription() const
  238. {
  239. if (auto* s = getCurrentSet())
  240. return s->name;
  241. return {};
  242. }
  243. String UndoManager::getRedoDescription() const
  244. {
  245. if (auto* s = getNextSet())
  246. return s->name;
  247. return {};
  248. }
  249. Time UndoManager::getTimeOfUndoTransaction() const
  250. {
  251. if (auto* s = getCurrentSet())
  252. return s->time;
  253. return {};
  254. }
  255. Time UndoManager::getTimeOfRedoTransaction() const
  256. {
  257. if (auto* s = getNextSet())
  258. return s->time;
  259. return Time::getCurrentTime();
  260. }
  261. bool UndoManager::undoCurrentTransactionOnly()
  262. {
  263. if ((! newTransaction) && undo())
  264. {
  265. restoreStashedFutureTransactions();
  266. return true;
  267. }
  268. return false;
  269. }
  270. void UndoManager::getActionsInCurrentTransaction (Array<const UndoableAction*>& actionsFound) const
  271. {
  272. if (! newTransaction)
  273. if (const ActionSet* const s = getCurrentSet())
  274. for (int i = 0; i < s->actions.size(); ++i)
  275. actionsFound.add (s->actions.getUnchecked(i));
  276. }
  277. int UndoManager::getNumActionsInCurrentTransaction() const
  278. {
  279. if (! newTransaction)
  280. if (const ActionSet* const s = getCurrentSet())
  281. return s->actions.size();
  282. return 0;
  283. }
  284. } // namespace juce