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.

287 lines
8.1KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library - "Jules' Utility Class Extensions"
  4. Copyright 2004-11 by Raw Material Software Ltd.
  5. ------------------------------------------------------------------------------
  6. JUCE can be redistributed and/or modified under the terms of the GNU General
  7. Public License (Version 2), as published by the Free Software Foundation.
  8. A copy of the license is included in the JUCE distribution, or can be found
  9. online at www.gnu.org/licenses.
  10. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  11. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  12. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  13. ------------------------------------------------------------------------------
  14. To release a closed-source product which uses JUCE, commercial licenses are
  15. available: visit www.rawmaterialsoftware.com/juce for more information.
  16. ==============================================================================
  17. */
  18. struct UndoManager::ActionSet
  19. {
  20. ActionSet (const String& transactionName)
  21. : name (transactionName),
  22. time (Time::getCurrentTime())
  23. {}
  24. OwnedArray <UndoableAction> actions;
  25. String name;
  26. Time time;
  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. };
  49. //==============================================================================
  50. UndoManager::UndoManager (const int maxNumberOfUnitsToKeep,
  51. const int minimumTransactions)
  52. : totalUnitsStored (0),
  53. nextIndex (0),
  54. newTransaction (true),
  55. reentrancyCheck (false)
  56. {
  57. setMaxNumberOfStoredUnits (maxNumberOfUnitsToKeep,
  58. minimumTransactions);
  59. }
  60. UndoManager::~UndoManager()
  61. {
  62. }
  63. //==============================================================================
  64. void UndoManager::clearUndoHistory()
  65. {
  66. transactions.clear();
  67. totalUnitsStored = 0;
  68. nextIndex = 0;
  69. sendChangeMessage();
  70. }
  71. int UndoManager::getNumberOfUnitsTakenUpByStoredCommands() const
  72. {
  73. return totalUnitsStored;
  74. }
  75. void UndoManager::setMaxNumberOfStoredUnits (const int maxNumberOfUnitsToKeep,
  76. const int minimumTransactions)
  77. {
  78. maxNumUnitsToKeep = jmax (1, maxNumberOfUnitsToKeep);
  79. minimumTransactionsToKeep = jmax (1, minimumTransactions);
  80. }
  81. //==============================================================================
  82. bool UndoManager::perform (UndoableAction* const newAction, const String& actionName)
  83. {
  84. if (newAction != nullptr)
  85. {
  86. ScopedPointer<UndoableAction> action (newAction);
  87. if (reentrancyCheck)
  88. {
  89. jassertfalse; // don't call perform() recursively from the UndoableAction::perform()
  90. // or undo() methods, or else these actions will be discarded!
  91. return false;
  92. }
  93. if (actionName.isNotEmpty())
  94. currentTransactionName = actionName;
  95. if (action->perform())
  96. {
  97. ActionSet* actionSet = getCurrentSet();
  98. if (actionSet != nullptr && ! newTransaction)
  99. {
  100. if (UndoableAction* const lastAction = actionSet->actions.getLast())
  101. {
  102. if (UndoableAction* const coalescedAction = lastAction->createCoalescedAction (action))
  103. {
  104. action = coalescedAction;
  105. totalUnitsStored -= lastAction->getSizeInUnits();
  106. actionSet->actions.removeLast();
  107. }
  108. }
  109. }
  110. else
  111. {
  112. actionSet = new ActionSet (currentTransactionName);
  113. transactions.insert (nextIndex, actionSet);
  114. ++nextIndex;
  115. }
  116. totalUnitsStored += action->getSizeInUnits();
  117. actionSet->actions.add (action.release());
  118. newTransaction = false;
  119. clearFutureTransactions();
  120. sendChangeMessage();
  121. return true;
  122. }
  123. }
  124. return false;
  125. }
  126. void UndoManager::clearFutureTransactions()
  127. {
  128. while (nextIndex < transactions.size())
  129. {
  130. totalUnitsStored -= transactions.getLast()->getTotalSize();
  131. transactions.removeLast();
  132. }
  133. while (nextIndex > 0
  134. && totalUnitsStored > maxNumUnitsToKeep
  135. && transactions.size() > minimumTransactionsToKeep)
  136. {
  137. totalUnitsStored -= transactions.getFirst()->getTotalSize();
  138. transactions.remove (0);
  139. --nextIndex;
  140. // if this fails, then some actions may not be returning
  141. // consistent results from their getSizeInUnits() method
  142. jassert (totalUnitsStored >= 0);
  143. }
  144. }
  145. void UndoManager::beginNewTransaction (const String& actionName)
  146. {
  147. newTransaction = true;
  148. currentTransactionName = actionName;
  149. }
  150. void UndoManager::setCurrentTransactionName (const String& newName)
  151. {
  152. currentTransactionName = newName;
  153. }
  154. //==============================================================================
  155. UndoManager::ActionSet* UndoManager::getCurrentSet() const noexcept { return transactions [nextIndex - 1]; }
  156. UndoManager::ActionSet* UndoManager::getNextSet() const noexcept { return transactions [nextIndex]; }
  157. bool UndoManager::canUndo() const { return getCurrentSet() != nullptr; }
  158. bool UndoManager::canRedo() const { return getNextSet() != nullptr; }
  159. bool UndoManager::undo()
  160. {
  161. if (const ActionSet* const s = getCurrentSet())
  162. {
  163. const ScopedValueSetter<bool> setter (reentrancyCheck, true);
  164. if (s->undo())
  165. --nextIndex;
  166. else
  167. clearUndoHistory();
  168. beginNewTransaction();
  169. sendChangeMessage();
  170. return true;
  171. }
  172. return false;
  173. }
  174. bool UndoManager::redo()
  175. {
  176. if (const ActionSet* const s = getNextSet())
  177. {
  178. const ScopedValueSetter<bool> setter (reentrancyCheck, true);
  179. if (s->perform())
  180. ++nextIndex;
  181. else
  182. clearUndoHistory();
  183. beginNewTransaction();
  184. sendChangeMessage();
  185. return true;
  186. }
  187. return false;
  188. }
  189. String UndoManager::getUndoDescription() const
  190. {
  191. if (const ActionSet* const s = getCurrentSet())
  192. return s->name;
  193. return String::empty;
  194. }
  195. String UndoManager::getRedoDescription() const
  196. {
  197. if (const ActionSet* const s = getNextSet())
  198. return s->name;
  199. return String::empty;
  200. }
  201. Time UndoManager::getTimeOfUndoTransaction() const
  202. {
  203. if (const ActionSet* const s = getCurrentSet())
  204. return s->time;
  205. return Time();
  206. }
  207. Time UndoManager::getTimeOfRedoTransaction() const
  208. {
  209. if (const ActionSet* const s = getNextSet())
  210. return s->time;
  211. return Time::getCurrentTime();
  212. }
  213. bool UndoManager::undoCurrentTransactionOnly()
  214. {
  215. return newTransaction ? false : undo();
  216. }
  217. void UndoManager::getActionsInCurrentTransaction (Array <const UndoableAction*>& actionsFound) const
  218. {
  219. if (! newTransaction)
  220. if (const ActionSet* const s = getCurrentSet())
  221. for (int i = 0; i < s->actions.size(); ++i)
  222. actionsFound.add (s->actions.getUnchecked(i));
  223. }
  224. int UndoManager::getNumActionsInCurrentTransaction() const
  225. {
  226. if (! newTransaction)
  227. if (const ActionSet* const s = getCurrentSet())
  228. return s->actions.size();
  229. return 0;
  230. }