Convert undo library into a submodule

This commit is contained in:
David Capello 2016-08-01 10:20:26 -03:00
parent 45fcd9e95e
commit 5ea955df4d
12 changed files with 13 additions and 486 deletions

3
.gitmodules vendored
View File

@ -33,3 +33,6 @@
[submodule "src/observable"]
path = src/observable
url = https://github.com/dacap/observable.git
[submodule "src/undo"]
path = src/undo
url = https://github.com/aseprite/undo.git

View File

@ -85,16 +85,20 @@ set(OBSERVABLE_TESTS OFF CACHE BOOL "Compile observable tests")
add_subdirectory(observable)
include_directories(observable)
# Disable clip examples
set(CLIP_EXAMPLES OFF CACHE BOOL "Compile clip examples")
add_subdirectory(clip)
# Disable undo tests
set(UNDO_TESTS OFF CACHE BOOL "Compile undo tests")
add_subdirectory(undo)
# Our base library
add_subdirectory(base)
# Directory where base/config.h file is located
include_directories(${BASE_INCLUDE_DIR})
# Disable clip examples
set(CLIP_EXAMPLES OFF CACHE BOOL "Compile clip examples")
add_subdirectory(clip)
add_subdirectory(cfg)
add_subdirectory(css)
add_subdirectory(doc)
@ -108,7 +112,6 @@ add_subdirectory(render)
add_subdirectory(script)
add_subdirectory(she)
add_subdirectory(ui)
add_subdirectory(undo)
if(ENABLE_UPDATER)
add_subdirectory(updater)
@ -178,7 +181,6 @@ if(ENABLE_TESTS)
include(FindTests)
find_tests(base base-lib)
find_tests(undo undo-lib)
find_tests(gfx gfx-lib)
find_tests(doc doc-lib)
find_tests(render render-lib)

View File

@ -459,7 +459,7 @@ target_link_libraries(app-lib
script-lib
she
ui-lib
undo-lib
undo
${TINYXML_LIBRARY}
${JPEG_LIBRARIES}
${GIF_LIBRARIES}

1
src/undo Submodule

@ -0,0 +1 @@
Subproject commit f39b188e29d0f9adaa49c8705c0f492939d967a9

View File

@ -1,5 +0,0 @@
# Aseprite Undo Library
# Copyright (C) 2015 David Capello
add_library(undo-lib
undo_history.cpp)

View File

@ -1,20 +0,0 @@
Copyright (c) 2015 David Capello
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -1,4 +0,0 @@
# Aseprite Undo Library
*Copyright (C) 2015 David Capello*
> Distributed under [MIT license](LICENSE.txt)

View File

@ -1,23 +0,0 @@
// Aseprite Undo Library
// Copyright (C) 2015 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#ifndef UNDO_UNDO_COMMAND_H_INCLUDED
#define UNDO_UNDO_COMMAND_H_INCLUDED
#pragma once
namespace undo {
class UndoCommand {
public:
virtual ~UndoCommand() { }
virtual void undo() = 0;
virtual void redo() = 0;
virtual void dispose() = 0;
};
} // namespace undo
#endif // UNDO_UNDO_COMMAND_H_INCLUDED

View File

@ -1,153 +0,0 @@
// Aseprite Undo Library
// Copyright (C) 2015 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "undo/undo_history.h"
#include "undo/undo_command.h"
#include "undo/undo_state.h"
#include <cassert>
#include <stack>
namespace undo {
UndoHistory::UndoHistory()
: m_first(nullptr)
, m_last(nullptr)
, m_cur(nullptr)
{
}
UndoHistory::~UndoHistory()
{
m_cur = nullptr;
clearRedo();
}
bool UndoHistory::canUndo() const
{
return m_cur != nullptr;
}
bool UndoHistory::canRedo() const
{
return m_cur != m_last;
}
void UndoHistory::undo()
{
assert(m_cur);
if (!m_cur)
return;
assert(
(m_cur != m_first && m_cur->m_prev) ||
(m_cur == m_first && !m_cur->m_prev));
moveTo(m_cur->m_prev);
}
void UndoHistory::redo()
{
if (!m_cur)
moveTo(m_first);
else
moveTo(m_cur->m_next);
}
void UndoHistory::clearRedo()
{
for (UndoState* state = m_last, *prev;
state && state != m_cur;
state = prev) {
prev = state->m_prev;
delete state;
}
if (m_cur) {
m_cur->m_next = nullptr;
m_last = m_cur;
}
else {
m_first = m_last = nullptr;
}
}
void UndoHistory::add(UndoCommand* cmd)
{
UndoState* state = new UndoState(cmd);
state->m_prev = m_last;
state->m_next = nullptr;
state->m_parent = m_cur;
if (!m_first)
m_first = state;
m_cur = m_last = state;
if (state->m_prev) {
assert(!state->m_prev->m_next);
state->m_prev->m_next = state;
}
}
const UndoState* UndoHistory::findCommonParent(const UndoState* a,
const UndoState* b)
{
const UndoState* pA = a;
const UndoState* pB = b;
if (pA == nullptr || pB == nullptr)
return nullptr;
while (pA != pB) {
pA = pA->m_parent;
if (!pA) {
pA = a;
pB = pB->m_parent;
if (!pB)
return nullptr;
}
}
return pA;
}
void UndoHistory::moveTo(const UndoState* new_state)
{
const UndoState* common = findCommonParent(m_cur, new_state);
if (m_cur) {
while (m_cur != common) {
m_cur->m_cmd->undo();
m_cur = m_cur->m_parent;
}
}
if (new_state) {
std::stack<const UndoState*> redo_parents;
const UndoState* p = new_state;
while (p != common) {
redo_parents.push(p);
p = p->m_parent;
}
while (!redo_parents.empty()) {
p = redo_parents.top();
redo_parents.pop();
p->m_cmd->redo();
}
}
m_cur = const_cast<UndoState*>(new_state);
}
} // namespace undo

View File

@ -1,47 +0,0 @@
// Aseprite Undo Library
// Copyright (C) 2015 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#ifndef UNDO_UNDO_HISTORY_H_INCLUDED
#define UNDO_UNDO_HISTORY_H_INCLUDED
#pragma once
namespace undo {
class UndoCommand;
class UndoState;
class UndoHistory {
public:
UndoHistory();
virtual ~UndoHistory();
const UndoState* firstState() const { return m_first; }
const UndoState* lastState() const { return m_last; }
const UndoState* currentState() const { return m_cur; }
void add(UndoCommand* cmd);
bool canUndo() const;
bool canRedo() const;
void undo();
void redo();
void clearRedo();
// This can be used to jump to a specific UndoState in the whole
// history.
void moveTo(const UndoState* new_state);
private:
const UndoState* findCommonParent(const UndoState* a,
const UndoState* b);
UndoState* m_first;
UndoState* m_last;
UndoState* m_cur; // Current action that can be undone
};
} // namespace undo
#endif // UNDO_UNDO_HISTORY_H_INCLUDED

View File

@ -1,45 +0,0 @@
// Aseprite Undo Library
// Copyright (C) 2015 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#ifndef UNDO_UNDO_STATE_H_INCLUDED
#define UNDO_UNDO_STATE_H_INCLUDED
#pragma once
#include "undo/undo_command.h"
namespace undo {
class UndoCommand;
class UndoHistory;
// Represents a state that can be undone. If we are in this state,
// is because the command was already executed.
class UndoState {
friend class UndoHistory;
public:
UndoState(UndoCommand* cmd)
: m_prev(nullptr)
, m_next(nullptr)
, m_parent(nullptr)
, m_cmd(cmd) {
}
~UndoState() {
if (m_cmd)
m_cmd->dispose();
}
UndoState* prev() const { return m_prev; }
UndoState* next() const { return m_next; }
UndoCommand* cmd() const { return m_cmd; }
private:
UndoState* m_prev;
UndoState* m_next;
UndoState* m_parent; // Parent state, after we undo
UndoCommand* m_cmd;
};
} // namespace undo
#endif // UNDO_UNDO_STATE_H_INCLUDED

View File

@ -1,182 +0,0 @@
// Aseprite Undo Library
// Copyright (C) 2015-2016 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <gtest/gtest.h>
#include "undo/undo_command.h"
#include "undo/undo_history.h"
using namespace undo;
class Cmd : public UndoCommand {
public:
Cmd(int& model, int redo_value, int undo_value)
: m_model(model)
, m_redo_value(redo_value)
, m_undo_value(undo_value) {
}
void redo() override { m_model = m_redo_value; }
void undo() override { m_model = m_undo_value; }
void dispose() override { }
private:
int& m_model;
int m_redo_value;
int m_undo_value;
};
TEST(Undo, Basics)
{
int model = 0;
Cmd cmd1(model, 1, 0);
Cmd cmd2(model, 2, 1);
EXPECT_EQ(0, model);
cmd1.redo();
EXPECT_EQ(1, model);
cmd2.redo();
EXPECT_EQ(2, model);
UndoHistory history;
EXPECT_FALSE(history.canUndo());
EXPECT_FALSE(history.canRedo());
history.add(&cmd1);
EXPECT_TRUE(history.canUndo());
EXPECT_FALSE(history.canRedo());
history.add(&cmd2);
EXPECT_TRUE(history.canUndo());
EXPECT_FALSE(history.canRedo());
history.undo();
EXPECT_EQ(1, model);
EXPECT_TRUE(history.canUndo());
EXPECT_TRUE(history.canRedo());
history.undo();
EXPECT_EQ(0, model);
EXPECT_FALSE(history.canUndo());
EXPECT_TRUE(history.canRedo());
history.redo();
EXPECT_EQ(1, model);
EXPECT_TRUE(history.canUndo());
EXPECT_TRUE(history.canRedo());
history.redo();
EXPECT_EQ(2, model);
EXPECT_TRUE(history.canUndo());
EXPECT_FALSE(history.canRedo());
}
TEST(Undo, Tree)
{
// 1 --- 2
// |
// ------ 3 --- 4
int model = 0;
Cmd cmd1(model, 1, 0);
Cmd cmd2(model, 2, 1);
Cmd cmd3(model, 3, 1);
Cmd cmd4(model, 4, 3);
UndoHistory history;
cmd1.redo(); history.add(&cmd1);
cmd2.redo(); history.add(&cmd2);
history.undo();
cmd3.redo(); history.add(&cmd3); // Creates a branch in the history
cmd4.redo(); history.add(&cmd4);
EXPECT_EQ(4, model);
history.undo();
EXPECT_EQ(3, model);
history.undo();
EXPECT_EQ(2, model);
history.undo();
EXPECT_EQ(1, model);
history.undo();
EXPECT_EQ(0, model);
EXPECT_FALSE(history.canUndo());
history.redo();
EXPECT_EQ(1, model);
history.redo();
EXPECT_EQ(2, model);
history.redo();
EXPECT_EQ(3, model);
history.redo();
EXPECT_EQ(4, model);
EXPECT_FALSE(history.canRedo());
}
TEST(Undo, ComplexTree)
{
// 1 --- 2 --- 3 --- 4 ------ 7 --- 8
// | |
// ------------- 5 --- 6
int model = 0;
Cmd cmd1(model, 1, 0);
Cmd cmd2(model, 2, 1);
Cmd cmd3(model, 3, 2);
Cmd cmd4(model, 4, 3);
Cmd cmd5(model, 5, 2);
Cmd cmd6(model, 6, 5);
Cmd cmd7(model, 7, 5);
Cmd cmd8(model, 8, 7);
UndoHistory history;
cmd1.redo(); history.add(&cmd1);
cmd2.redo(); history.add(&cmd2);
cmd3.redo(); history.add(&cmd3);
cmd4.redo(); history.add(&cmd4);
history.undo();
cmd5.redo(); history.add(&cmd5);
cmd6.redo(); history.add(&cmd6);
history.undo();
cmd7.redo(); history.add(&cmd7);
cmd8.redo(); history.add(&cmd8);
EXPECT_EQ(8, model);
history.undo();
EXPECT_EQ(7, model);
history.undo();
EXPECT_EQ(6, model);
history.undo();
EXPECT_EQ(5, model);
history.undo();
EXPECT_EQ(4, model);
history.undo();
EXPECT_EQ(3, model);
history.undo();
EXPECT_EQ(2, model);
history.undo();
EXPECT_EQ(1, model);
history.undo();
EXPECT_EQ(0, model);
EXPECT_FALSE(history.canUndo());
history.redo();
EXPECT_EQ(1, model);
history.redo();
EXPECT_EQ(2, model);
history.redo();
EXPECT_EQ(3, model);
history.redo();
EXPECT_EQ(4, model);
history.redo();
EXPECT_EQ(5, model);
history.redo();
EXPECT_EQ(6, model);
history.redo();
EXPECT_EQ(7, model);
history.redo();
EXPECT_EQ(8, model);
EXPECT_FALSE(history.canRedo());
}
int main(int argc, char** argv)
{
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}