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.

345 lines
8.8KB

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