mirror of
https://github.com/aseprite/aseprite.git
synced 2025-04-24 09:02:31 +00:00
Add "Undo History" command (fix #739)
Added member functions to undo::UndoHistory and app::DocumentUndo to iterate all states of the undo history. Also we’ve added app::DocumentUndoObserver to see when new states are added in the undo history.
This commit is contained in:
parent
05b70d2538
commit
2eada35a38
9
data/widgets/undo_history.xml
Normal file
9
data/widgets/undo_history.xml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<!-- ASEPRITE -->
|
||||||
|
<!-- Copyright (C) 2015 by David Capello -->
|
||||||
|
<gui>
|
||||||
|
<window id="undo_history" text="Undo History">
|
||||||
|
<view id="view" expansive="true" width="80" height="100">
|
||||||
|
<listbox id="actions" />
|
||||||
|
</view>
|
||||||
|
</window>
|
||||||
|
</gui>
|
@ -252,6 +252,7 @@ add_library(app-lib
|
|||||||
commands/cmd_timeline.cpp
|
commands/cmd_timeline.cpp
|
||||||
commands/cmd_toggle_preview.cpp
|
commands/cmd_toggle_preview.cpp
|
||||||
commands/cmd_undo.cpp
|
commands/cmd_undo.cpp
|
||||||
|
commands/cmd_undo_history.cpp
|
||||||
commands/cmd_unlink_cel.cpp
|
commands/cmd_unlink_cel.cpp
|
||||||
commands/cmd_zoom.cpp
|
commands/cmd_zoom.cpp
|
||||||
commands/command.cpp
|
commands/command.cpp
|
||||||
|
239
src/app/commands/cmd_undo_history.cpp
Normal file
239
src/app/commands/cmd_undo_history.cpp
Normal file
@ -0,0 +1,239 @@
|
|||||||
|
// Aseprite
|
||||||
|
// Copyright (C) 2015 David Capello
|
||||||
|
//
|
||||||
|
// This program is free software; you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License version 2 as
|
||||||
|
// published by the Free Software Foundation.
|
||||||
|
|
||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include "config.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "app/cmd.h"
|
||||||
|
#include "app/commands/command.h"
|
||||||
|
#include "app/console.h"
|
||||||
|
#include "app/context.h"
|
||||||
|
#include "app/document.h"
|
||||||
|
#include "app/document_access.h"
|
||||||
|
#include "app/document_undo.h"
|
||||||
|
#include "app/document_undo_observer.h"
|
||||||
|
#include "base/bind.h"
|
||||||
|
#include "doc/context_observer.h"
|
||||||
|
#include "doc/documents_observer.h"
|
||||||
|
#include "doc/site.h"
|
||||||
|
#include "undo/undo_state.h"
|
||||||
|
|
||||||
|
#include "undo_history.xml.h"
|
||||||
|
|
||||||
|
namespace app {
|
||||||
|
|
||||||
|
class UndoHistoryWindow : public app::gen::UndoHistory,
|
||||||
|
public doc::ContextObserver,
|
||||||
|
public doc::DocumentsObserver,
|
||||||
|
public app::DocumentUndoObserver {
|
||||||
|
public:
|
||||||
|
class Item : public ui::ListItem {
|
||||||
|
public:
|
||||||
|
Item(const undo::UndoState* state)
|
||||||
|
: ui::ListItem(
|
||||||
|
(state ? static_cast<Cmd*>(state->cmd())->label():
|
||||||
|
std::string("Initial State"))),
|
||||||
|
m_state(state) {
|
||||||
|
}
|
||||||
|
const undo::UndoState* state() { return m_state; }
|
||||||
|
private:
|
||||||
|
const undo::UndoState* m_state;
|
||||||
|
};
|
||||||
|
|
||||||
|
UndoHistoryWindow(Context* ctx)
|
||||||
|
: m_ctx(ctx),
|
||||||
|
m_document(nullptr) {
|
||||||
|
actions()->Change.connect(&UndoHistoryWindow::onChangeAction, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
~UndoHistoryWindow() {
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool onProcessMessage(ui::Message* msg) override {
|
||||||
|
switch (msg->type()) {
|
||||||
|
|
||||||
|
case ui::kOpenMessage:
|
||||||
|
m_ctx->addObserver(this);
|
||||||
|
m_ctx->documents().addObserver(this);
|
||||||
|
if (m_ctx->activeDocument()) {
|
||||||
|
attachDocument(
|
||||||
|
static_cast<app::Document*>(m_ctx->activeDocument()));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ui::kCloseMessage:
|
||||||
|
if (m_document)
|
||||||
|
detachDocument();
|
||||||
|
m_ctx->documents().removeObserver(this);
|
||||||
|
m_ctx->removeObserver(this);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return app::gen::UndoHistory::onProcessMessage(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
void onChangeAction() {
|
||||||
|
Item* item = static_cast<Item*>(
|
||||||
|
actions()->getSelectedChild());
|
||||||
|
|
||||||
|
if (m_document &&
|
||||||
|
m_document->undoHistory()->currentState() != item->state()) {
|
||||||
|
try {
|
||||||
|
DocumentWriter writer(m_document, 100);
|
||||||
|
m_document->undoHistory()->moveToState(item->state());
|
||||||
|
m_document->generateMaskBoundaries();
|
||||||
|
m_document->notifyGeneralUpdate();
|
||||||
|
}
|
||||||
|
catch (const std::exception& ex) {
|
||||||
|
selectState(m_document->undoHistory()->currentState());
|
||||||
|
Console::showException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContextObserver
|
||||||
|
void onActiveSiteChange(const doc::Site& site) override {
|
||||||
|
if (m_document == site.document())
|
||||||
|
return;
|
||||||
|
|
||||||
|
attachDocument(
|
||||||
|
static_cast<app::Document*>(
|
||||||
|
const_cast<doc::Document*>(site.document())));
|
||||||
|
}
|
||||||
|
|
||||||
|
// DocumentsObserver
|
||||||
|
void onRemoveDocument(doc::Document* doc) override {
|
||||||
|
if (m_document && m_document == doc)
|
||||||
|
detachDocument();
|
||||||
|
}
|
||||||
|
|
||||||
|
// DocumentUndoObserver
|
||||||
|
void onAddUndoState(DocumentUndo* history) override {
|
||||||
|
ASSERT(history->currentState());
|
||||||
|
Item* item = new Item(history->currentState());
|
||||||
|
actions()->addChild(item);
|
||||||
|
actions()->layout();
|
||||||
|
view()->updateView();
|
||||||
|
actions()->selectChild(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
void onAfterUndo(DocumentUndo* history) override {
|
||||||
|
selectState(history->currentState());
|
||||||
|
}
|
||||||
|
|
||||||
|
void onAfterRedo(DocumentUndo* history) override {
|
||||||
|
selectState(history->currentState());
|
||||||
|
}
|
||||||
|
|
||||||
|
void onClearRedo(DocumentUndo* history) override {
|
||||||
|
refillList(history);
|
||||||
|
}
|
||||||
|
|
||||||
|
void attachDocument(app::Document* document) {
|
||||||
|
detachDocument();
|
||||||
|
|
||||||
|
m_document = document;
|
||||||
|
if (!document)
|
||||||
|
return;
|
||||||
|
|
||||||
|
DocumentUndo* history = m_document->undoHistory();
|
||||||
|
history->addObserver(this);
|
||||||
|
|
||||||
|
refillList(history);
|
||||||
|
}
|
||||||
|
|
||||||
|
void detachDocument() {
|
||||||
|
if (!m_document)
|
||||||
|
return;
|
||||||
|
|
||||||
|
clearList();
|
||||||
|
m_document->undoHistory()->removeObserver(this);
|
||||||
|
m_document = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void clearList() {
|
||||||
|
ui::Widget* child;
|
||||||
|
while ((child = actions()->getFirstChild()))
|
||||||
|
delete child;
|
||||||
|
|
||||||
|
actions()->layout();
|
||||||
|
view()->updateView();
|
||||||
|
}
|
||||||
|
|
||||||
|
void refillList(DocumentUndo* history) {
|
||||||
|
clearList();
|
||||||
|
|
||||||
|
// Create an item to reference the initial state (undo state == nullptr)
|
||||||
|
Item* current = new Item(nullptr);
|
||||||
|
actions()->addChild(current);
|
||||||
|
|
||||||
|
const undo::UndoState* state = history->firstState();
|
||||||
|
while (state) {
|
||||||
|
Item* item = new Item(state);
|
||||||
|
actions()->addChild(item);
|
||||||
|
if (state == history->currentState())
|
||||||
|
current = item;
|
||||||
|
|
||||||
|
state = state->next();
|
||||||
|
}
|
||||||
|
|
||||||
|
actions()->layout();
|
||||||
|
view()->updateView();
|
||||||
|
if (current)
|
||||||
|
actions()->selectChild(current);
|
||||||
|
}
|
||||||
|
|
||||||
|
void selectState(const undo::UndoState* state) {
|
||||||
|
for (auto child : actions()->getChildren()) {
|
||||||
|
Item* item = static_cast<Item*>(child);
|
||||||
|
if (item->state() == state) {
|
||||||
|
actions()->selectChild(item);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Context* m_ctx;
|
||||||
|
app::Document* m_document;
|
||||||
|
};
|
||||||
|
|
||||||
|
class UndoHistoryCommand : public Command {
|
||||||
|
public:
|
||||||
|
UndoHistoryCommand();
|
||||||
|
Command* clone() const override { return new UndoHistoryCommand(*this); }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void onExecute(Context* ctx) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
static UndoHistoryWindow* g_window = NULL;
|
||||||
|
|
||||||
|
UndoHistoryCommand::UndoHistoryCommand()
|
||||||
|
: Command("UndoHistory",
|
||||||
|
"Undo History",
|
||||||
|
CmdUIOnlyFlag)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void UndoHistoryCommand::onExecute(Context* ctx)
|
||||||
|
{
|
||||||
|
if (!g_window)
|
||||||
|
g_window = new UndoHistoryWindow(ctx);
|
||||||
|
|
||||||
|
if (g_window->isVisible())
|
||||||
|
g_window->setVisible(false);
|
||||||
|
else
|
||||||
|
g_window->openWindow();
|
||||||
|
}
|
||||||
|
|
||||||
|
Command* CommandFactory::createUndoHistoryCommand()
|
||||||
|
{
|
||||||
|
return new UndoHistoryCommand;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace app
|
@ -118,5 +118,6 @@ FOR_EACH_COMMAND(TiledMode)
|
|||||||
FOR_EACH_COMMAND(Timeline)
|
FOR_EACH_COMMAND(Timeline)
|
||||||
FOR_EACH_COMMAND(TogglePreview)
|
FOR_EACH_COMMAND(TogglePreview)
|
||||||
FOR_EACH_COMMAND(Undo)
|
FOR_EACH_COMMAND(Undo)
|
||||||
|
FOR_EACH_COMMAND(UndoHistory)
|
||||||
FOR_EACH_COMMAND(UnlinkCel)
|
FOR_EACH_COMMAND(UnlinkCel)
|
||||||
FOR_EACH_COMMAND(Zoom)
|
FOR_EACH_COMMAND(Zoom)
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
#include "app/app.h"
|
#include "app/app.h"
|
||||||
#include "app/cmd.h"
|
#include "app/cmd.h"
|
||||||
#include "app/cmd_transaction.h"
|
#include "app/cmd_transaction.h"
|
||||||
|
#include "app/document_undo_observer.h"
|
||||||
#include "app/pref/preferences.h"
|
#include "app/pref/preferences.h"
|
||||||
#include "doc/context.h"
|
#include "doc/context.h"
|
||||||
#include "undo/undo_history.h"
|
#include "undo/undo_history.h"
|
||||||
@ -43,10 +44,11 @@ void DocumentUndo::add(CmdTransaction* cmd)
|
|||||||
// A linear undo history is the default behavior
|
// A linear undo history is the default behavior
|
||||||
if (!App::instance() ||
|
if (!App::instance() ||
|
||||||
!App::instance()->preferences().undo.allowNonlinearHistory()) {
|
!App::instance()->preferences().undo.allowNonlinearHistory()) {
|
||||||
m_undoHistory.clearRedo();
|
clearRedo();
|
||||||
}
|
}
|
||||||
|
|
||||||
m_undoHistory.add(cmd);
|
m_undoHistory.add(cmd);
|
||||||
|
notifyObservers(&DocumentUndoObserver::onAddUndoState, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DocumentUndo::canUndo() const
|
bool DocumentUndo::canUndo() const
|
||||||
@ -61,17 +63,20 @@ bool DocumentUndo::canRedo() const
|
|||||||
|
|
||||||
void DocumentUndo::undo()
|
void DocumentUndo::undo()
|
||||||
{
|
{
|
||||||
return m_undoHistory.undo();
|
m_undoHistory.undo();
|
||||||
|
notifyObservers(&DocumentUndoObserver::onAfterUndo, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DocumentUndo::redo()
|
void DocumentUndo::redo()
|
||||||
{
|
{
|
||||||
return m_undoHistory.redo();
|
m_undoHistory.redo();
|
||||||
|
notifyObservers(&DocumentUndoObserver::onAfterRedo, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DocumentUndo::clearRedo()
|
void DocumentUndo::clearRedo()
|
||||||
{
|
{
|
||||||
return m_undoHistory.clearRedo();
|
m_undoHistory.clearRedo();
|
||||||
|
notifyObservers(&DocumentUndoObserver::onClearRedo, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DocumentUndo::isSavedState() const
|
bool DocumentUndo::isSavedState() const
|
||||||
@ -137,6 +142,11 @@ Cmd* DocumentUndo::lastExecutedCmd() const
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DocumentUndo::moveToState(const undo::UndoState* state)
|
||||||
|
{
|
||||||
|
m_undoHistory.moveTo(state);
|
||||||
|
}
|
||||||
|
|
||||||
const undo::UndoState* DocumentUndo::nextUndo() const
|
const undo::UndoState* DocumentUndo::nextUndo() const
|
||||||
{
|
{
|
||||||
return m_undoHistory.currentState();
|
return m_undoHistory.currentState();
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "base/disable_copying.h"
|
#include "base/disable_copying.h"
|
||||||
|
#include "base/observable.h"
|
||||||
#include "base/unique_ptr.h"
|
#include "base/unique_ptr.h"
|
||||||
#include "doc/sprite_position.h"
|
#include "doc/sprite_position.h"
|
||||||
#include "undo/undo_history.h"
|
#include "undo/undo_history.h"
|
||||||
@ -25,8 +26,9 @@ namespace app {
|
|||||||
|
|
||||||
class Cmd;
|
class Cmd;
|
||||||
class CmdTransaction;
|
class CmdTransaction;
|
||||||
|
class DocumentUndoObserver;
|
||||||
|
|
||||||
class DocumentUndo {
|
class DocumentUndo : public base::Observable<DocumentUndoObserver> {
|
||||||
public:
|
public:
|
||||||
DocumentUndo();
|
DocumentUndo();
|
||||||
|
|
||||||
@ -55,6 +57,11 @@ namespace app {
|
|||||||
|
|
||||||
int* savedCounter() { return &m_savedCounter; }
|
int* savedCounter() { return &m_savedCounter; }
|
||||||
|
|
||||||
|
const undo::UndoState* firstState() const { return m_undoHistory.firstState(); }
|
||||||
|
const undo::UndoState* currentState() const { return m_undoHistory.currentState(); }
|
||||||
|
|
||||||
|
void moveToState(const undo::UndoState* state);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const undo::UndoState* nextUndo() const;
|
const undo::UndoState* nextUndo() const;
|
||||||
const undo::UndoState* nextRedo() const;
|
const undo::UndoState* nextRedo() const;
|
||||||
|
31
src/app/document_undo_observer.h
Normal file
31
src/app/document_undo_observer.h
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
// Aseprite
|
||||||
|
// Copyright (C) 2015 David Capello
|
||||||
|
//
|
||||||
|
// This program is free software; you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License version 2 as
|
||||||
|
// published by the Free Software Foundation.
|
||||||
|
|
||||||
|
#ifndef APP_DOCUMENT_UNDO_OBSERVER_H_INCLUDED
|
||||||
|
#define APP_DOCUMENT_UNDO_OBSERVER_H_INCLUDED
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace undo {
|
||||||
|
class UndoState;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace app {
|
||||||
|
|
||||||
|
class DocumentUndo;
|
||||||
|
|
||||||
|
class DocumentUndoObserver {
|
||||||
|
public:
|
||||||
|
virtual ~DocumentUndoObserver() { }
|
||||||
|
virtual void onAddUndoState(DocumentUndo* history) = 0;
|
||||||
|
virtual void onAfterUndo(DocumentUndo* history) = 0;
|
||||||
|
virtual void onAfterRedo(DocumentUndo* history) = 0;
|
||||||
|
virtual void onClearRedo(DocumentUndo* history) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace app
|
||||||
|
|
||||||
|
#endif
|
@ -98,10 +98,11 @@ void UndoHistory::add(UndoCommand* cmd)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
UndoState* UndoHistory::findCommonParent(UndoState* a, UndoState* b)
|
const UndoState* UndoHistory::findCommonParent(const UndoState* a,
|
||||||
|
const UndoState* b)
|
||||||
{
|
{
|
||||||
UndoState* pA = a;
|
const UndoState* pA = a;
|
||||||
UndoState* pB = b;
|
const UndoState* pB = b;
|
||||||
|
|
||||||
if (pA == nullptr || pB == nullptr)
|
if (pA == nullptr || pB == nullptr)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
@ -119,9 +120,9 @@ UndoState* UndoHistory::findCommonParent(UndoState* a, UndoState* b)
|
|||||||
return pA;
|
return pA;
|
||||||
}
|
}
|
||||||
|
|
||||||
void UndoHistory::moveTo(UndoState* new_state)
|
void UndoHistory::moveTo(const UndoState* new_state)
|
||||||
{
|
{
|
||||||
UndoState* common = findCommonParent(m_cur, new_state);
|
const UndoState* common = findCommonParent(m_cur, new_state);
|
||||||
|
|
||||||
if (m_cur) {
|
if (m_cur) {
|
||||||
while (m_cur != common) {
|
while (m_cur != common) {
|
||||||
@ -131,8 +132,8 @@ void UndoHistory::moveTo(UndoState* new_state)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (new_state) {
|
if (new_state) {
|
||||||
std::stack<UndoState*> redo_parents;
|
std::stack<const UndoState*> redo_parents;
|
||||||
UndoState* p = new_state;
|
const UndoState* p = new_state;
|
||||||
while (p != common) {
|
while (p != common) {
|
||||||
redo_parents.push(p);
|
redo_parents.push(p);
|
||||||
p = p->m_parent;
|
p = p->m_parent;
|
||||||
@ -146,7 +147,7 @@ void UndoHistory::moveTo(UndoState* new_state)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
m_cur = new_state;
|
m_cur = const_cast<UndoState*>(new_state);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace undo
|
} // namespace undo
|
||||||
|
@ -27,12 +27,15 @@ namespace undo {
|
|||||||
bool canRedo() const;
|
bool canRedo() const;
|
||||||
void undo();
|
void undo();
|
||||||
void redo();
|
void redo();
|
||||||
|
|
||||||
void clearRedo();
|
void clearRedo();
|
||||||
|
|
||||||
|
// This can be used to jump to a specific UndoState in the whole
|
||||||
|
// history.
|
||||||
|
void moveTo(const UndoState* new_state);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
UndoState* findCommonParent(UndoState* a, UndoState* b);
|
const UndoState* findCommonParent(const UndoState* a,
|
||||||
void moveTo(UndoState* new_state);
|
const UndoState* b);
|
||||||
|
|
||||||
UndoState* m_first;
|
UndoState* m_first;
|
||||||
UndoState* m_last;
|
UndoState* m_last;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user