1// Aseprite
2// Copyright (C) 2020-2021 Igara Studio S.A.
3// Copyright (C) 2001-2018 David Capello
4//
5// This program is distributed under the terms of
6// the End-User License Agreement for Aseprite.
7
8#ifdef HAVE_CONFIG_H
9#include "config.h"
10#endif
11
12#include "app/app.h"
13#include "app/commands/command.h"
14#include "app/context_access.h"
15#include "app/doc_undo.h"
16#include "app/ini_file.h"
17#include "app/modules/editors.h"
18#include "app/modules/gui.h"
19#include "app/modules/palettes.h"
20#include "app/pref/preferences.h"
21#include "app/ui/editor/editor.h"
22#include "app/ui/status_bar.h"
23#include "base/thread.h"
24#include "doc/sprite.h"
25#include "ui/manager.h"
26#include "ui/system.h"
27
28#ifdef ENABLE_UI
29#include "app/ui/timeline/timeline.h"
30#endif
31
32namespace app {
33
34class UndoCommand : public Command {
35public:
36 enum Type { Undo, Redo };
37
38 UndoCommand(Type type);
39
40protected:
41 bool onEnabled(Context* context) override;
42 void onExecute(Context* context) override;
43
44private:
45 Type m_type;
46};
47
48UndoCommand::UndoCommand(Type type)
49 : Command((type == Undo ? CommandId::Undo():
50 CommandId::Redo()), CmdUIOnlyFlag)
51 , m_type(type)
52{
53}
54
55bool UndoCommand::onEnabled(Context* context)
56{
57 const ContextReader reader(context);
58 const Doc* doc(reader.document());
59 return
60 doc &&
61 ((m_type == Undo ? doc->undoHistory()->canUndo():
62 doc->undoHistory()->canRedo()));
63}
64
65void UndoCommand::onExecute(Context* context)
66{
67 ContextWriter writer(context);
68 Doc* document(writer.document());
69 DocUndo* undo = document->undoHistory();
70
71#ifdef ENABLE_UI
72 Sprite* sprite = document->sprite();
73 SpritePosition spritePosition;
74 const bool gotoModified =
75 (Preferences::instance().undo.gotoModified() &&
76 context->isUIAvailable() && current_editor);
77 if (gotoModified) {
78 SpritePosition currentPosition(writer.site()->layer(),
79 writer.site()->frame());
80
81 if (m_type == Undo)
82 spritePosition = undo->nextUndoSpritePosition();
83 else
84 spritePosition = undo->nextRedoSpritePosition();
85
86 if (spritePosition != currentPosition) {
87 Layer* selectLayer = spritePosition.layer();
88 if (selectLayer)
89 current_editor->setLayer(selectLayer);
90 current_editor->setFrame(spritePosition.frame());
91
92 // Draw the current layer/frame (which is not undone yet) so the
93 // user can see the doUndo/doRedo effect.
94 current_editor->drawSpriteClipped(
95 gfx::Region(gfx::Rect(0, 0, sprite->width(), sprite->height())));
96
97 current_editor->display()->flipDisplay();
98 base::this_thread::sleep_for(0.01);
99 }
100 }
101
102 // Get the stream to deserialize the document range after executing
103 // the undo/redo action. We cannot yet deserialize the document
104 // range because there could be inexistent layers.
105 std::istream* docRangeStream;
106 if (m_type == Undo)
107 docRangeStream = undo->nextUndoDocRange();
108 else
109 docRangeStream = undo->nextRedoDocRange();
110
111 StatusBar* statusbar = StatusBar::instance();
112 if (statusbar) {
113 std::string msg;
114 if (m_type == Undo)
115 msg = "Undid " + undo->nextUndoLabel();
116 else
117 msg = "Redid " + undo->nextRedoLabel();
118 if (Preferences::instance().undo.showTooltip())
119 statusbar->showTip(1000, msg);
120 else
121 statusbar->setStatusText(0, msg);
122 }
123#endif // ENABLE_UI
124
125 // Effectively undo/redo.
126 if (m_type == Undo)
127 undo->undo();
128 else
129 undo->redo();
130
131#ifdef ENABLE_UI
132 // After redo/undo, we retry to change the current SpritePosition
133 // (because new frames/layers could be added, positions that we
134 // weren't able to reach before the undo).
135 if (gotoModified) {
136 Site newSite = context->activeSite();
137 SpritePosition currentPosition(
138 newSite.layer(),
139 newSite.frame());
140
141 if (spritePosition != currentPosition) {
142 Layer* selectLayer = spritePosition.layer();
143 if (selectLayer)
144 current_editor->setLayer(selectLayer);
145 current_editor->setFrame(spritePosition.frame());
146 }
147 }
148
149 // Update timeline range. We've to deserialize the DocRange at
150 // this point when objects (possible layers) are re-created after
151 // the undo and we can deserialize them.
152 if (docRangeStream) {
153 Timeline* timeline = App::instance()->timeline();
154 if (timeline) {
155 DocRange docRange;
156 if (docRange.read(*docRangeStream))
157 timeline->setRange(docRange);
158 }
159 }
160#endif // ENABLE_UI
161
162 document->generateMaskBoundaries();
163 document->setExtraCel(ExtraCelRef(nullptr));
164
165#ifdef ENABLE_UI
166 update_screen_for_document(document);
167#endif
168 set_current_palette(writer.palette(), false);
169}
170
171Command* CommandFactory::createUndoCommand()
172{
173 return new UndoCommand(UndoCommand::Undo);
174}
175
176Command* CommandFactory::createRedoCommand()
177{
178 return new UndoCommand(UndoCommand::Redo);
179}
180
181} // namespace app
182