2015-02-12 23:16:25 +08:00
|
|
|
// Aseprite
|
2022-05-24 03:19:06 +08:00
|
|
|
// Copyright (C) 2022 Igara Studio S.A.
|
2018-06-09 01:40:02 +08:00
|
|
|
// Copyright (C) 2001-2018 David Capello
|
2015-02-12 23:16:25 +08:00
|
|
|
//
|
2016-08-27 04:02:58 +08:00
|
|
|
// This program is distributed under the terms of
|
|
|
|
|
// the End-User License Agreement for Aseprite.
|
2012-07-08 12:25:26 +08:00
|
|
|
|
2013-08-06 08:20:19 +08:00
|
|
|
#ifdef HAVE_CONFIG_H
|
2012-07-08 12:25:26 +08:00
|
|
|
#include "config.h"
|
2013-08-06 08:20:19 +08:00
|
|
|
#endif
|
2012-07-08 12:25:26 +08:00
|
|
|
|
2018-07-07 13:55:27 +08:00
|
|
|
#include "app/doc_undo.h"
|
2012-07-08 12:25:26 +08:00
|
|
|
|
2015-01-19 09:05:33 +08:00
|
|
|
#include "app/app.h"
|
|
|
|
|
#include "app/cmd.h"
|
|
|
|
|
#include "app/cmd_transaction.h"
|
2018-07-07 13:47:42 +08:00
|
|
|
#include "app/context.h"
|
2018-07-07 13:55:27 +08:00
|
|
|
#include "app/doc_undo_observer.h"
|
2015-01-19 09:05:33 +08:00
|
|
|
#include "app/pref/preferences.h"
|
2017-10-26 04:25:23 +08:00
|
|
|
#include "base/mem_utils.h"
|
2015-01-19 09:16:29 +08:00
|
|
|
#include "undo/undo_history.h"
|
|
|
|
|
#include "undo/undo_state.h"
|
2012-07-08 12:25:26 +08:00
|
|
|
|
|
|
|
|
#include <cassert>
|
2012-07-11 05:26:08 +08:00
|
|
|
#include <stdexcept>
|
2012-07-08 12:25:26 +08:00
|
|
|
|
2017-10-26 04:25:23 +08:00
|
|
|
#define UNDO_TRACE(...)
|
2017-10-27 08:42:25 +08:00
|
|
|
#define STATE_CMD(state) (static_cast<CmdTransaction*>(state->cmd()))
|
2017-10-26 04:25:23 +08:00
|
|
|
|
2013-08-06 08:20:19 +08:00
|
|
|
namespace app {
|
|
|
|
|
|
2018-07-07 13:55:27 +08:00
|
|
|
DocUndo::DocUndo()
|
2017-10-26 04:25:23 +08:00
|
|
|
: m_undoHistory(this)
|
2012-07-08 12:25:26 +08:00
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
2018-07-07 13:55:27 +08:00
|
|
|
void DocUndo::setContext(Context* ctx)
|
2014-07-31 11:19:58 +08:00
|
|
|
{
|
|
|
|
|
m_ctx = ctx;
|
|
|
|
|
}
|
|
|
|
|
|
2018-07-07 13:55:27 +08:00
|
|
|
void DocUndo::add(CmdTransaction* cmd)
|
2015-01-19 09:05:33 +08:00
|
|
|
{
|
|
|
|
|
ASSERT(cmd);
|
2017-10-26 04:25:23 +08:00
|
|
|
UNDO_TRACE("UNDO: Add state <%s> of %s to %s\n",
|
|
|
|
|
cmd->label().c_str(),
|
|
|
|
|
base::get_pretty_memory_size(cmd->memSize()).c_str(),
|
|
|
|
|
base::get_pretty_memory_size(m_totalUndoSize).c_str());
|
2015-01-19 09:05:33 +08:00
|
|
|
|
|
|
|
|
// A linear undo history is the default behavior
|
|
|
|
|
if (!App::instance() ||
|
|
|
|
|
!App::instance()->preferences().undo.allowNonlinearHistory()) {
|
2015-10-20 22:27:05 +08:00
|
|
|
clearRedo();
|
2015-01-19 09:05:33 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_undoHistory.add(cmd);
|
2017-10-26 04:25:23 +08:00
|
|
|
m_totalUndoSize += cmd->memSize();
|
|
|
|
|
|
2018-07-07 13:55:27 +08:00
|
|
|
notify_observers(&DocUndoObserver::onAddUndoState, this);
|
|
|
|
|
notify_observers(&DocUndoObserver::onTotalUndoSizeChange, this);
|
2017-10-26 04:25:23 +08:00
|
|
|
|
|
|
|
|
if (App::instance()) {
|
|
|
|
|
const size_t undoLimitSize =
|
|
|
|
|
int(App::instance()->preferences().undo.sizeLimit())
|
|
|
|
|
* 1024 * 1024;
|
|
|
|
|
|
|
|
|
|
// If undo limit is 0, it means "no limit", so we ignore the
|
|
|
|
|
// complete logic to discard undo states.
|
|
|
|
|
if (undoLimitSize > 0 &&
|
|
|
|
|
m_totalUndoSize > undoLimitSize) {
|
|
|
|
|
UNDO_TRACE("UNDO: Reducing undo history from %s to %s\n",
|
|
|
|
|
base::get_pretty_memory_size(m_totalUndoSize).c_str(),
|
|
|
|
|
base::get_pretty_memory_size(undoLimitSize).c_str());
|
|
|
|
|
|
|
|
|
|
while (m_undoHistory.firstState() &&
|
|
|
|
|
m_totalUndoSize > undoLimitSize) {
|
|
|
|
|
if (!m_undoHistory.deleteFirstState())
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
UNDO_TRACE("UNDO: New undo size %s\n",
|
|
|
|
|
base::get_pretty_memory_size(m_totalUndoSize).c_str());
|
2015-01-19 09:05:33 +08:00
|
|
|
}
|
|
|
|
|
|
2018-07-07 13:55:27 +08:00
|
|
|
bool DocUndo::canUndo() const
|
2012-07-08 12:25:26 +08:00
|
|
|
{
|
2015-01-19 09:05:33 +08:00
|
|
|
return m_undoHistory.canUndo();
|
2012-07-08 12:25:26 +08:00
|
|
|
}
|
|
|
|
|
|
2018-07-07 13:55:27 +08:00
|
|
|
bool DocUndo::canRedo() const
|
2012-07-08 12:25:26 +08:00
|
|
|
{
|
2015-01-19 09:05:33 +08:00
|
|
|
return m_undoHistory.canRedo();
|
2012-07-08 12:25:26 +08:00
|
|
|
}
|
|
|
|
|
|
2018-07-07 13:55:27 +08:00
|
|
|
void DocUndo::undo()
|
2012-07-08 12:25:26 +08:00
|
|
|
{
|
2022-09-29 21:57:40 +08:00
|
|
|
const size_t oldSize = m_totalUndoSize;
|
2017-10-27 08:42:25 +08:00
|
|
|
{
|
2022-09-29 22:04:05 +08:00
|
|
|
const undo::UndoState* state = nextUndo();
|
|
|
|
|
ASSERT(state);
|
2022-09-29 21:57:40 +08:00
|
|
|
const Cmd* cmd = STATE_CMD(state);
|
|
|
|
|
m_totalUndoSize -= cmd->memSize();
|
2017-10-27 08:42:25 +08:00
|
|
|
m_undoHistory.undo();
|
2022-09-29 21:57:40 +08:00
|
|
|
m_totalUndoSize += cmd->memSize();
|
2017-10-27 08:42:25 +08:00
|
|
|
}
|
2022-09-29 21:57:40 +08:00
|
|
|
// This notification could execute a script that modifies the sprite
|
|
|
|
|
// again (e.g. a script that is listening the "change" event, check
|
|
|
|
|
// the SpriteEvents class). If the sprite is modified, the "cmd" is
|
|
|
|
|
// not valid anymore.
|
|
|
|
|
notify_observers(&DocUndoObserver::onCurrentUndoStateChange, this);
|
2017-10-27 08:42:25 +08:00
|
|
|
if (m_totalUndoSize != oldSize)
|
2018-07-07 13:55:27 +08:00
|
|
|
notify_observers(&DocUndoObserver::onTotalUndoSizeChange, this);
|
2012-07-08 12:25:26 +08:00
|
|
|
}
|
|
|
|
|
|
2018-07-07 13:55:27 +08:00
|
|
|
void DocUndo::redo()
|
2012-07-08 12:25:26 +08:00
|
|
|
{
|
2022-09-29 22:04:05 +08:00
|
|
|
const size_t oldSize = m_totalUndoSize;
|
2017-10-27 08:42:25 +08:00
|
|
|
{
|
2022-09-29 22:04:05 +08:00
|
|
|
const undo::UndoState* state = nextRedo();
|
|
|
|
|
ASSERT(state);
|
|
|
|
|
const Cmd* cmd = STATE_CMD(state);
|
|
|
|
|
m_totalUndoSize -= cmd->memSize();
|
2017-10-27 08:42:25 +08:00
|
|
|
m_undoHistory.redo();
|
2022-09-29 22:04:05 +08:00
|
|
|
m_totalUndoSize += cmd->memSize();
|
2017-10-27 08:42:25 +08:00
|
|
|
}
|
2022-09-29 22:04:05 +08:00
|
|
|
notify_observers(&DocUndoObserver::onCurrentUndoStateChange, this);
|
2017-10-27 08:42:25 +08:00
|
|
|
if (m_totalUndoSize != oldSize)
|
2018-07-07 13:55:27 +08:00
|
|
|
notify_observers(&DocUndoObserver::onTotalUndoSizeChange, this);
|
2012-07-08 12:25:26 +08:00
|
|
|
}
|
|
|
|
|
|
2018-07-07 13:55:27 +08:00
|
|
|
void DocUndo::clearRedo()
|
2012-07-08 12:25:26 +08:00
|
|
|
{
|
2022-05-24 03:19:06 +08:00
|
|
|
// Do nothing
|
|
|
|
|
if (currentState() == lastState())
|
|
|
|
|
return;
|
|
|
|
|
|
2015-10-20 22:27:05 +08:00
|
|
|
m_undoHistory.clearRedo();
|
2018-07-07 13:55:27 +08:00
|
|
|
notify_observers(&DocUndoObserver::onClearRedo, this);
|
2012-07-08 12:25:26 +08:00
|
|
|
}
|
|
|
|
|
|
2022-11-02 05:57:19 +08:00
|
|
|
bool DocUndo::isInSavedState() const
|
2012-07-08 12:25:26 +08:00
|
|
|
{
|
2022-11-02 05:57:19 +08:00
|
|
|
return (!m_savedStateIsLost &&
|
|
|
|
|
m_savedState == currentState());
|
2012-07-08 12:25:26 +08:00
|
|
|
}
|
|
|
|
|
|
2018-07-07 13:55:27 +08:00
|
|
|
void DocUndo::markSavedState()
|
2012-07-08 12:25:26 +08:00
|
|
|
{
|
2022-11-02 05:57:19 +08:00
|
|
|
m_savedState = currentState();
|
2015-01-19 09:05:33 +08:00
|
|
|
m_savedStateIsLost = false;
|
2022-11-02 05:57:19 +08:00
|
|
|
notify_observers(&DocUndoObserver::onNewSavedState, this);
|
2012-07-08 12:25:26 +08:00
|
|
|
}
|
|
|
|
|
|
2018-07-07 13:55:27 +08:00
|
|
|
void DocUndo::impossibleToBackToSavedState()
|
2014-05-03 04:04:55 +08:00
|
|
|
{
|
2022-11-02 05:57:19 +08:00
|
|
|
// Now there is no state related to the disk state.
|
|
|
|
|
m_savedState = nullptr;
|
2015-01-19 09:05:33 +08:00
|
|
|
m_savedStateIsLost = true;
|
2022-11-02 05:57:19 +08:00
|
|
|
notify_observers(&DocUndoObserver::onNewSavedState, this);
|
2014-05-03 04:04:55 +08:00
|
|
|
}
|
|
|
|
|
|
2018-07-07 13:55:27 +08:00
|
|
|
std::string DocUndo::nextUndoLabel() const
|
2012-07-08 12:25:26 +08:00
|
|
|
{
|
2015-01-19 09:16:29 +08:00
|
|
|
const undo::UndoState* state = nextUndo();
|
2015-01-19 09:05:33 +08:00
|
|
|
if (state)
|
2017-10-27 08:42:25 +08:00
|
|
|
return STATE_CMD(state)->label();
|
2015-01-19 09:05:33 +08:00
|
|
|
else
|
|
|
|
|
return "";
|
2012-07-08 12:25:26 +08:00
|
|
|
}
|
|
|
|
|
|
2018-07-07 13:55:27 +08:00
|
|
|
std::string DocUndo::nextRedoLabel() const
|
2012-07-08 12:25:26 +08:00
|
|
|
{
|
2015-01-19 09:16:29 +08:00
|
|
|
const undo::UndoState* state = nextRedo();
|
2015-01-19 09:05:33 +08:00
|
|
|
if (state)
|
2017-10-27 08:42:25 +08:00
|
|
|
return STATE_CMD(state)->label();
|
2015-01-19 09:05:33 +08:00
|
|
|
else
|
|
|
|
|
return "";
|
2012-07-08 12:25:26 +08:00
|
|
|
}
|
|
|
|
|
|
2018-07-07 13:55:27 +08:00
|
|
|
SpritePosition DocUndo::nextUndoSpritePosition() const
|
2012-07-11 05:26:08 +08:00
|
|
|
{
|
2015-01-19 09:16:29 +08:00
|
|
|
const undo::UndoState* state = nextUndo();
|
2015-01-19 09:05:33 +08:00
|
|
|
if (state)
|
2017-10-27 08:42:25 +08:00
|
|
|
return STATE_CMD(state)->spritePositionBeforeExecute();
|
2015-01-19 09:05:33 +08:00
|
|
|
else
|
|
|
|
|
return SpritePosition();
|
2012-07-11 05:26:08 +08:00
|
|
|
}
|
|
|
|
|
|
2018-07-07 13:55:27 +08:00
|
|
|
SpritePosition DocUndo::nextRedoSpritePosition() const
|
2012-07-11 05:26:08 +08:00
|
|
|
{
|
2015-01-19 09:16:29 +08:00
|
|
|
const undo::UndoState* state = nextRedo();
|
2015-01-19 09:05:33 +08:00
|
|
|
if (state)
|
2017-10-27 08:42:25 +08:00
|
|
|
return STATE_CMD(state)->spritePositionAfterExecute();
|
2015-01-19 09:05:33 +08:00
|
|
|
else
|
|
|
|
|
return SpritePosition();
|
2012-07-11 05:26:08 +08:00
|
|
|
}
|
|
|
|
|
|
2018-07-07 21:07:21 +08:00
|
|
|
std::istream* DocUndo::nextUndoDocRange() const
|
2018-06-09 01:40:02 +08:00
|
|
|
{
|
|
|
|
|
const undo::UndoState* state = nextUndo();
|
|
|
|
|
if (state)
|
|
|
|
|
return STATE_CMD(state)->documentRangeBeforeExecute();
|
|
|
|
|
else
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
2018-07-07 21:07:21 +08:00
|
|
|
std::istream* DocUndo::nextRedoDocRange() const
|
2018-06-09 01:40:02 +08:00
|
|
|
{
|
|
|
|
|
const undo::UndoState* state = nextRedo();
|
|
|
|
|
if (state)
|
|
|
|
|
return STATE_CMD(state)->documentRangeAfterExecute();
|
|
|
|
|
else
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
2018-07-07 13:55:27 +08:00
|
|
|
Cmd* DocUndo::lastExecutedCmd() const
|
2012-07-11 05:26:08 +08:00
|
|
|
{
|
2015-01-19 09:16:29 +08:00
|
|
|
const undo::UndoState* state = m_undoHistory.currentState();
|
2015-01-19 09:05:33 +08:00
|
|
|
if (state)
|
2017-10-27 08:42:25 +08:00
|
|
|
return STATE_CMD(state);
|
2015-01-19 09:05:33 +08:00
|
|
|
else
|
|
|
|
|
return NULL;
|
2012-07-11 05:26:08 +08:00
|
|
|
}
|
|
|
|
|
|
2018-07-07 13:55:27 +08:00
|
|
|
void DocUndo::moveToState(const undo::UndoState* state)
|
2015-10-20 22:27:05 +08:00
|
|
|
{
|
|
|
|
|
m_undoHistory.moveTo(state);
|
2022-09-29 22:04:05 +08:00
|
|
|
|
|
|
|
|
// After onCurrentUndoStateChange don't use the "state" argument, it
|
|
|
|
|
// might be deleted because some script might have modified the
|
|
|
|
|
// sprite on its "change" event.
|
2018-07-07 13:55:27 +08:00
|
|
|
notify_observers(&DocUndoObserver::onCurrentUndoStateChange, this);
|
2017-10-27 08:42:25 +08:00
|
|
|
|
|
|
|
|
// Recalculate the total undo size
|
|
|
|
|
size_t oldSize = m_totalUndoSize;
|
|
|
|
|
m_totalUndoSize = 0;
|
|
|
|
|
const undo::UndoState* s = m_undoHistory.firstState();
|
|
|
|
|
while (s) {
|
|
|
|
|
m_totalUndoSize += STATE_CMD(s)->memSize();
|
|
|
|
|
s = s->next();
|
|
|
|
|
}
|
|
|
|
|
if (m_totalUndoSize != oldSize)
|
2018-07-07 13:55:27 +08:00
|
|
|
notify_observers(&DocUndoObserver::onTotalUndoSizeChange, this);
|
2015-10-20 22:27:05 +08:00
|
|
|
}
|
|
|
|
|
|
2018-07-07 13:55:27 +08:00
|
|
|
const undo::UndoState* DocUndo::nextUndo() const
|
2012-07-11 05:26:08 +08:00
|
|
|
{
|
2015-01-19 09:05:33 +08:00
|
|
|
return m_undoHistory.currentState();
|
2012-07-11 05:26:08 +08:00
|
|
|
}
|
|
|
|
|
|
2018-07-07 13:55:27 +08:00
|
|
|
const undo::UndoState* DocUndo::nextRedo() const
|
2012-07-08 12:25:26 +08:00
|
|
|
{
|
2015-01-19 09:16:29 +08:00
|
|
|
const undo::UndoState* state = m_undoHistory.currentState();
|
2015-01-19 09:05:33 +08:00
|
|
|
if (state)
|
|
|
|
|
return state->next();
|
2012-07-11 05:26:08 +08:00
|
|
|
else
|
2015-01-19 09:05:33 +08:00
|
|
|
return m_undoHistory.firstState();
|
2012-07-08 12:25:26 +08:00
|
|
|
}
|
2013-08-06 08:20:19 +08:00
|
|
|
|
2018-07-07 13:55:27 +08:00
|
|
|
void DocUndo::onDeleteUndoState(undo::UndoState* state)
|
2017-10-26 04:25:23 +08:00
|
|
|
{
|
2017-11-22 22:11:31 +08:00
|
|
|
ASSERT(state);
|
2017-10-27 08:42:25 +08:00
|
|
|
Cmd* cmd = STATE_CMD(state);
|
2017-10-26 04:25:23 +08:00
|
|
|
|
|
|
|
|
UNDO_TRACE("UNDO: Deleting undo state <%s> of %s from %s\n",
|
|
|
|
|
cmd->label().c_str(),
|
|
|
|
|
base::get_pretty_memory_size(cmd->memSize()).c_str(),
|
|
|
|
|
base::get_pretty_memory_size(m_totalUndoSize).c_str());
|
|
|
|
|
|
|
|
|
|
m_totalUndoSize -= cmd->memSize();
|
2018-07-07 13:55:27 +08:00
|
|
|
notify_observers(&DocUndoObserver::onDeleteUndoState, this, state);
|
2022-11-02 05:57:19 +08:00
|
|
|
|
|
|
|
|
// Mark this document as impossible to match the version on disk
|
|
|
|
|
// because we're just going to delete the saved state.
|
|
|
|
|
if (m_savedState == state)
|
|
|
|
|
impossibleToBackToSavedState();
|
2017-10-26 04:25:23 +08:00
|
|
|
}
|
|
|
|
|
|
2013-08-06 08:20:19 +08:00
|
|
|
} // namespace app
|