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