1// Aseprite
2// Copyright (C) 2018-2019 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/transaction.h"
13
14#include "app/cmd_transaction.h"
15#include "app/context_access.h"
16#include "app/doc.h"
17#include "app/doc_undo.h"
18#include "app/modules/palettes.h"
19#include "doc/sprite.h"
20#include "ui/manager.h"
21#include "ui/system.h"
22
23#define TX_TRACE(...)
24
25namespace app {
26
27using namespace doc;
28
29Transaction::Transaction(
30 Context* ctx,
31 Doc* doc,
32 const std::string& label,
33 Modification modification)
34 : m_ctx(ctx)
35 , m_doc(doc)
36 , m_undo(nullptr)
37 , m_cmds(nullptr)
38 , m_changes(Changes::kNone)
39{
40 TX_TRACE("TX: Start <%s> (%s)\n",
41 label.c_str(),
42 modification == ModifyDocument ? "modifies document":
43 "doesn't modify document");
44
45 m_doc->add_observer(this);
46 m_undo = m_doc->undoHistory();
47
48 m_cmds = new CmdTransaction(label,
49 modification == Modification::ModifyDocument,
50 m_undo->savedCounter());
51
52 // Here we are executing an empty CmdTransaction, just to save the
53 // SpritePosition. Sub-cmds are executed then one by one, in
54 // Transaction::execute()
55 m_cmds->execute(m_ctx);
56}
57
58Transaction::~Transaction()
59{
60 try {
61 // If it isn't committed, we have to rollback all changes.
62 if (m_cmds)
63 rollback(nullptr);
64 }
65 catch (...) {
66 // Just avoid throwing an exception in the dtor (just in case
67 // rollback() failed).
68
69 // TODO logging error
70 }
71
72 m_doc->remove_observer(this);
73}
74
75// Used to set the document range after all the transaction is
76// executed and before the commit. This range is stored in
77// CmdTransaction to recover it on Edit > Redo.
78void Transaction::setNewDocRange(const DocRange& range)
79{
80 ASSERT(m_cmds);
81 m_cmds->setNewDocRange(range);
82}
83
84void Transaction::commit()
85{
86 // This assert can fail when we run scripts in batch mode
87 //ui::assert_ui_thread();
88
89 ASSERT(m_cmds);
90 TX_TRACE("TX: Commit <%s>\n", m_cmds->label().c_str());
91
92 m_cmds->updateSpritePositionAfter();
93#ifdef ENABLE_UI
94 const SpritePosition sprPos = m_cmds->spritePositionAfterExecute();
95#endif
96
97 m_undo->add(m_cmds);
98 m_cmds = nullptr;
99
100 // Process changes
101 if (int(m_changes) & int(Changes::kSelection)) {
102 m_doc->resetTransformation();
103 m_doc->generateMaskBoundaries();
104 }
105
106#ifdef ENABLE_UI
107 if (int(m_changes) & int(Changes::kColorChange)) {
108 ASSERT(m_doc);
109 ASSERT(m_doc->sprite());
110
111 Palette* pal = m_doc->sprite()->palette(sprPos.frame());
112 ASSERT(pal);
113 if (pal)
114 set_current_palette(pal, false);
115 else
116 set_current_palette(nullptr, false);
117
118 if (m_ctx->isUIAvailable())
119 ui::Manager::getDefault()->invalidate();
120 }
121#endif
122}
123
124void Transaction::rollbackAndStartAgain()
125{
126 auto newCmds = m_cmds->moveToEmptyCopy();
127 rollback(newCmds);
128 newCmds->execute(m_ctx);
129}
130
131void Transaction::rollback(CmdTransaction* newCmds)
132{
133 ASSERT(m_cmds);
134 TX_TRACE("TX: Rollback <%s>\n", m_cmds->label().c_str());
135
136 m_cmds->undo();
137
138 delete m_cmds;
139 m_cmds = newCmds;
140}
141
142void Transaction::execute(Cmd* cmd)
143{
144 try {
145 cmd->execute(m_ctx);
146 }
147 catch (...) {
148 delete cmd;
149 throw;
150 }
151
152 try {
153 m_cmds->add(cmd);
154 }
155 catch (...) {
156 cmd->undo();
157 delete cmd;
158 throw;
159 }
160}
161
162void Transaction::onSelectionChanged(DocEvent& ev)
163{
164 m_changes = Changes(int(m_changes) | int(Changes::kSelection));
165}
166
167void Transaction::onColorSpaceChanged(DocEvent& ev)
168{
169 m_changes = Changes(int(m_changes) | int(Changes::kColorChange));
170}
171
172void Transaction::onPaletteChanged(DocEvent& ev)
173{
174 m_changes = Changes(int(m_changes) | int(Changes::kColorChange));
175}
176
177} // namespace app
178