The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
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.

348 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. struct UndoManager::ActionSet
  20. {
  21. ActionSet (const String& transactionName)
  22. : name (transactionName),
  23. time (Time::getCurrentTime())
  24. {}
  25. bool perform() const
  26. {
  27. for (int i = 0; i < actions.size(); ++i)
  28. if (! actions.getUnchecked(i)->perform())
  29. return false;
  30. return true;
  31. }
  32. bool undo() const
  33. {
  34. for (int i = actions.size(); --i >= 0;)
  35. if (! actions.getUnchecked(i)->undo())
  36. return false;
  37. return true;
  38. }
  39. int getTotalSize() const
  40. {
  41. int total = 0;
  42. for (int i = actions.size(); --i >= 0;)
  43. total += actions.getUnchecked(i)->getSizeInUnits();
  44. return total;
  45. }
  46. OwnedArray<UndoableAction> actions;
  47. String name;
  48. Time time;
  49. };
  50. //==============================================================================
  51. UndoManager::UndoManager (const int maxNumberOfUnitsToKeep,
  52. const int minimumTransactions)
  53. : totalUnitsStored (0),
  54. nextIndex (0),
  55. newTransaction (true),
  56. reentrancyCheck (false)
  57. {
  58. setMaxNumberOfStoredUnits (maxNumberOfUnitsToKeep,
  59. minimumTransactions);
  60. }
  61. UndoManager::~UndoManager()
  62. {
  63. }
  64. //==============================================================================
  65. void UndoManager::clearUndoHistory()
  66. {
  67. transactions.clear();
  68. totalUnitsStored = 0;
  69. nextIndex = 0;
  70. sendChangeMessage();
  71. }
  72. int UndoManager::getNumberOfUnitsTakenUpByStoredCommands() const
  73. {
  74. return totalUnitsStored;
  75. }
  76. void UndoManager::setMaxNumberOfStoredUnits (const int maxNumberOfUnitsToKeep,
  77. const int minimumTransactions)
  78. {
  79. maxNumUnitsToKeep = jmax (1, maxNumberOfUnitsToKeep);
  80. minimumTransactionsToKeep = jmax (1, minimumTransactions);
  81. }
  82. //==============================================================================
  83. bool UndoManager::perform (UndoableAction* const newAction, const String& actionName)
  84. {
  85. if (perform (newAction))
  86. {
  87. if (actionName.isNotEmpty())
  88. setCurrentTransactionName (actionName);
  89. return true;
  90. }
  91. return false;
  92. }
  93. bool UndoManager::perform (UndoableAction* const newAction)
  94. {
  95. if (newAction != nullptr)
  96. {
  97. ScopedPointer<UndoableAction> action (newAction);
  98. if (reentrancyCheck)
  99. {
  100. jassertfalse; // don't call perform() recursively from the UndoableAction::perform()
  101. // or undo() methods, or else these actions will be discarded!
  102. return false;
  103. }
  104. if (action->perform())
  105. {
  106. ActionSet* actionSet = getCurrentSet();
  107. if (actionSet != nullptr && ! newTransaction)
  108. {
  109. if (UndoableAction* const lastAction = actionSet->actions.getLast())
  110. {
  111. if (UndoableAction* const coalescedAction = lastAction->createCoalescedAction (action))
  112. {
  113. action = coalescedAction;
  114. totalUnitsStored -= lastAction->getSizeInUnits();
  115. actionSet->actions.removeLast();
  116. }
  117. }
  118. }
  119. else
  120. {
  121. actionSet = new ActionSet (newTransactionName);
  122. transactions.insert (nextIndex, actionSet);
  123. ++nextIndex;
  124. }
  125. totalUnitsStored += action->getSizeInUnits();
  126. actionSet->actions.add (action.release());
  127. newTransaction = false;
  128. moveFutureTransactionsToStash();
  129. dropOldTransactionsIfTooLarge();
  130. sendChangeMessage();
  131. return true;
  132. }
  133. }
  134. return false;
  135. }
  136. void UndoManager::moveFutureTransactionsToStash()
  137. {
  138. if (nextIndex < transactions.size())
  139. {
  140. stashedFutureTransactions.clear();
  141. while (nextIndex < transactions.size())
  142. {
  143. ActionSet* removed = transactions.removeAndReturn (nextIndex);
  144. stashedFutureTransactions.add (removed);
  145. totalUnitsStored -= removed->getTotalSize();
  146. }
  147. }
  148. }
  149. void UndoManager::restoreStashedFutureTransactions()
  150. {
  151. while (nextIndex < transactions.size())
  152. {
  153. totalUnitsStored -= transactions.getUnchecked (nextIndex)->getTotalSize();
  154. transactions.remove (nextIndex);
  155. }
  156. for (int i = 0; i < stashedFutureTransactions.size(); ++i)
  157. {
  158. ActionSet* action = stashedFutureTransactions.removeAndReturn (i);
  159. totalUnitsStored += action->getTotalSize();
  160. transactions.add (action);
  161. }
  162. stashedFutureTransactions.clearQuick (false);
  163. }
  164. void UndoManager::dropOldTransactionsIfTooLarge()
  165. {
  166. while (nextIndex > 0
  167. && totalUnitsStored > maxNumUnitsToKeep
  168. && transactions.size() > minimumTransactionsToKeep)
  169. {
  170. totalUnitsStored -= transactions.getFirst()->getTotalSize();
  171. transactions.remove (0);
  172. --nextIndex;
  173. // if this fails, then some actions may not be returning
  174. // consistent results from their getSizeInUnits() method
  175. jassert (totalUnitsStored >= 0);
  176. }
  177. }
  178. void UndoManager::beginNewTransaction() noexcept
  179. {
  180. beginNewTransaction (String());
  181. }
  182. void UndoManager::beginNewTransaction (const String& actionName) noexcept
  183. {
  184. newTransaction = true;
  185. newTransactionName = actionName;
  186. }
  187. void UndoManager::setCurrentTransactionName (const String& newName) noexcept
  188. {
  189. if (newTransaction)
  190. newTransactionName = newName;
  191. else if (ActionSet* action = getCurrentSet())
  192. action->name = newName;
  193. }
  194. String UndoManager::getCurrentTransactionName() const noexcept
  195. {
  196. if (ActionSet* action = getCurrentSet())
  197. return action->name;
  198. return newTransactionName;
  199. }
  200. //==============================================================================
  201. UndoManager::ActionSet* UndoManager::getCurrentSet() const noexcept { return transactions [nextIndex - 1]; }
  202. UndoManager::ActionSet* UndoManager::getNextSet() const noexcept { return transactions [nextIndex]; }
  203. bool UndoManager::canUndo() const noexcept { return getCurrentSet() != nullptr; }
  204. bool UndoManager::canRedo() const noexcept { return getNextSet() != nullptr; }
  205. bool UndoManager::undo()
  206. {
  207. if (const ActionSet* const s = getCurrentSet())
  208. {
  209. const ScopedValueSetter<bool> setter (reentrancyCheck, true);
  210. if (s->undo())
  211. --nextIndex;
  212. else
  213. clearUndoHistory();
  214. beginNewTransaction();
  215. sendChangeMessage();
  216. return true;
  217. }
  218. return false;
  219. }
  220. bool UndoManager::redo()
  221. {
  222. if (const ActionSet* const s = getNextSet())
  223. {
  224. const ScopedValueSetter<bool> setter (reentrancyCheck, true);
  225. if (s->perform())
  226. ++nextIndex;
  227. else
  228. clearUndoHistory();
  229. beginNewTransaction();
  230. sendChangeMessage();
  231. return true;
  232. }
  233. return false;
  234. }
  235. String UndoManager::getUndoDescription() const
  236. {
  237. if (auto* s = getCurrentSet())
  238. return s->name;
  239. return {};
  240. }
  241. String UndoManager::getRedoDescription() const
  242. {
  243. if (auto* s = getNextSet())
  244. return s->name;
  245. return {};
  246. }
  247. Time UndoManager::getTimeOfUndoTransaction() const
  248. {
  249. if (auto* s = getCurrentSet())
  250. return s->time;
  251. return {};
  252. }
  253. Time UndoManager::getTimeOfRedoTransaction() const
  254. {
  255. if (auto* s = getNextSet())
  256. return s->time;
  257. return Time::getCurrentTime();
  258. }
  259. bool UndoManager::undoCurrentTransactionOnly()
  260. {
  261. if ((! newTransaction) && undo())
  262. {
  263. restoreStashedFutureTransactions();
  264. return true;
  265. }
  266. return false;
  267. }
  268. void UndoManager::getActionsInCurrentTransaction (Array<const UndoableAction*>& actionsFound) const
  269. {
  270. if (! newTransaction)
  271. if (const ActionSet* const s = getCurrentSet())
  272. for (int i = 0; i < s->actions.size(); ++i)
  273. actionsFound.add (s->actions.getUnchecked(i));
  274. }
  275. int UndoManager::getNumActionsInCurrentTransaction() const
  276. {
  277. if (! newTransaction)
  278. if (const ActionSet* const s = getCurrentSet())
  279. return s->actions.size();
  280. return 0;
  281. }