1 // Aseprite
2 // Copyright (C) 2001-2018  David Capello
3 //
4 // This program is distributed under the terms of
5 // the End-User License Agreement for Aseprite.
6 
7 #ifdef HAVE_CONFIG_H
8 #include "config.h"
9 #endif
10 
11 #include "app/doc_undo.h"
12 
13 #include "app/app.h"
14 #include "app/cmd.h"
15 #include "app/cmd_transaction.h"
16 #include "app/context.h"
17 #include "app/doc_undo_observer.h"
18 #include "app/pref/preferences.h"
19 #include "base/mem_utils.h"
20 #include "undo/undo_history.h"
21 #include "undo/undo_state.h"
22 
23 #include <cassert>
24 #include <stdexcept>
25 
26 #define UNDO_TRACE(...)
27 #define STATE_CMD(state) (static_cast<CmdTransaction*>(state->cmd()))
28 
29 namespace app {
30 
DocUndo()31 DocUndo::DocUndo()
32   : m_undoHistory(this)
33   , m_ctx(nullptr)
34   , m_totalUndoSize(0)
35   , m_savedCounter(0)
36   , m_savedStateIsLost(false)
37 {
38 }
39 
setContext(Context * ctx)40 void DocUndo::setContext(Context* ctx)
41 {
42   m_ctx = ctx;
43 }
44 
add(CmdTransaction * cmd)45 void DocUndo::add(CmdTransaction* cmd)
46 {
47   ASSERT(cmd);
48   UNDO_TRACE("UNDO: Add state <%s> of %s to %s\n",
49              cmd->label().c_str(),
50              base::get_pretty_memory_size(cmd->memSize()).c_str(),
51              base::get_pretty_memory_size(m_totalUndoSize).c_str());
52 
53   // A linear undo history is the default behavior
54   if (!App::instance() ||
55       !App::instance()->preferences().undo.allowNonlinearHistory()) {
56     clearRedo();
57   }
58 
59   m_undoHistory.add(cmd);
60   m_totalUndoSize += cmd->memSize();
61 
62   notify_observers(&DocUndoObserver::onAddUndoState, this);
63   notify_observers(&DocUndoObserver::onTotalUndoSizeChange, this);
64 
65   if (App::instance()) {
66     const size_t undoLimitSize =
67       int(App::instance()->preferences().undo.sizeLimit())
68       * 1024 * 1024;
69 
70     // If undo limit is 0, it means "no limit", so we ignore the
71     // complete logic to discard undo states.
72     if (undoLimitSize > 0 &&
73         m_totalUndoSize > undoLimitSize) {
74       UNDO_TRACE("UNDO: Reducing undo history from %s to %s\n",
75                  base::get_pretty_memory_size(m_totalUndoSize).c_str(),
76                  base::get_pretty_memory_size(undoLimitSize).c_str());
77 
78       while (m_undoHistory.firstState() &&
79              m_totalUndoSize > undoLimitSize) {
80         if (!m_undoHistory.deleteFirstState())
81           break;
82       }
83     }
84   }
85 
86   UNDO_TRACE("UNDO: New undo size %s\n",
87              base::get_pretty_memory_size(m_totalUndoSize).c_str());
88 }
89 
canUndo() const90 bool DocUndo::canUndo() const
91 {
92   return m_undoHistory.canUndo();
93 }
94 
canRedo() const95 bool DocUndo::canRedo() const
96 {
97   return m_undoHistory.canRedo();
98 }
99 
undo()100 void DocUndo::undo()
101 {
102   const undo::UndoState* state = nextUndo();
103   ASSERT(state);
104   const Cmd* cmd = STATE_CMD(state);
105   size_t oldSize = m_totalUndoSize;
106   m_totalUndoSize -= cmd->memSize();
107   {
108     m_undoHistory.undo();
109     notify_observers(&DocUndoObserver::onCurrentUndoStateChange, this);
110   }
111   m_totalUndoSize += cmd->memSize();
112   if (m_totalUndoSize != oldSize)
113     notify_observers(&DocUndoObserver::onTotalUndoSizeChange, this);
114 }
115 
redo()116 void DocUndo::redo()
117 {
118   const undo::UndoState* state = nextRedo();
119   ASSERT(state);
120   const Cmd* cmd = STATE_CMD(state);
121   size_t oldSize = m_totalUndoSize;
122   m_totalUndoSize -= cmd->memSize();
123   {
124     m_undoHistory.redo();
125     notify_observers(&DocUndoObserver::onCurrentUndoStateChange, this);
126   }
127   m_totalUndoSize += cmd->memSize();
128   if (m_totalUndoSize != oldSize)
129     notify_observers(&DocUndoObserver::onTotalUndoSizeChange, this);
130 }
131 
clearRedo()132 void DocUndo::clearRedo()
133 {
134   m_undoHistory.clearRedo();
135   notify_observers(&DocUndoObserver::onClearRedo, this);
136 }
137 
isSavedState() const138 bool DocUndo::isSavedState() const
139 {
140   return (!m_savedStateIsLost && m_savedCounter == 0);
141 }
142 
markSavedState()143 void DocUndo::markSavedState()
144 {
145   m_savedCounter = 0;
146   m_savedStateIsLost = false;
147 }
148 
impossibleToBackToSavedState()149 void DocUndo::impossibleToBackToSavedState()
150 {
151   m_savedStateIsLost = true;
152 }
153 
nextUndoLabel() const154 std::string DocUndo::nextUndoLabel() const
155 {
156   const undo::UndoState* state = nextUndo();
157   if (state)
158     return STATE_CMD(state)->label();
159   else
160     return "";
161 }
162 
nextRedoLabel() const163 std::string DocUndo::nextRedoLabel() const
164 {
165   const undo::UndoState* state = nextRedo();
166   if (state)
167     return STATE_CMD(state)->label();
168   else
169     return "";
170 }
171 
nextUndoSpritePosition() const172 SpritePosition DocUndo::nextUndoSpritePosition() const
173 {
174   const undo::UndoState* state = nextUndo();
175   if (state)
176     return STATE_CMD(state)->spritePositionBeforeExecute();
177   else
178     return SpritePosition();
179 }
180 
nextRedoSpritePosition() const181 SpritePosition DocUndo::nextRedoSpritePosition() const
182 {
183   const undo::UndoState* state = nextRedo();
184   if (state)
185     return STATE_CMD(state)->spritePositionAfterExecute();
186   else
187     return SpritePosition();
188 }
189 
nextUndoDocRange() const190 std::istream* DocUndo::nextUndoDocRange() const
191 {
192   const undo::UndoState* state = nextUndo();
193   if (state)
194     return STATE_CMD(state)->documentRangeBeforeExecute();
195   else
196     return nullptr;
197 }
198 
nextRedoDocRange() const199 std::istream* DocUndo::nextRedoDocRange() const
200 {
201   const undo::UndoState* state = nextRedo();
202   if (state)
203     return STATE_CMD(state)->documentRangeAfterExecute();
204   else
205     return nullptr;
206 }
207 
lastExecutedCmd() const208 Cmd* DocUndo::lastExecutedCmd() const
209 {
210   const undo::UndoState* state = m_undoHistory.currentState();
211   if (state)
212     return STATE_CMD(state);
213   else
214     return NULL;
215 }
216 
moveToState(const undo::UndoState * state)217 void DocUndo::moveToState(const undo::UndoState* state)
218 {
219   m_undoHistory.moveTo(state);
220   notify_observers(&DocUndoObserver::onCurrentUndoStateChange, this);
221 
222   // Recalculate the total undo size
223   size_t oldSize = m_totalUndoSize;
224   m_totalUndoSize = 0;
225   const undo::UndoState* s = m_undoHistory.firstState();
226   while (s) {
227     m_totalUndoSize += STATE_CMD(s)->memSize();
228     s = s->next();
229   }
230   if (m_totalUndoSize != oldSize)
231     notify_observers(&DocUndoObserver::onTotalUndoSizeChange, this);
232 }
233 
nextUndo() const234 const undo::UndoState* DocUndo::nextUndo() const
235 {
236   return m_undoHistory.currentState();
237 }
238 
nextRedo() const239 const undo::UndoState* DocUndo::nextRedo() const
240 {
241   const undo::UndoState* state = m_undoHistory.currentState();
242   if (state)
243     return state->next();
244   else
245     return m_undoHistory.firstState();
246 }
247 
onDeleteUndoState(undo::UndoState * state)248 void DocUndo::onDeleteUndoState(undo::UndoState* state)
249 {
250   ASSERT(state);
251   Cmd* cmd = STATE_CMD(state);
252 
253   UNDO_TRACE("UNDO: Deleting undo state <%s> of %s from %s\n",
254              cmd->label().c_str(),
255              base::get_pretty_memory_size(cmd->memSize()).c_str(),
256              base::get_pretty_memory_size(m_totalUndoSize).c_str());
257 
258   m_totalUndoSize -= cmd->memSize();
259   notify_observers(&DocUndoObserver::onDeleteUndoState, this, state);
260 }
261 
262 } // namespace app
263