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.

346 lines
9.4KB

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