2015-10-20 14:27:05 +00:00
|
|
|
// Aseprite
|
2018-06-08 18:52:10 +00:00
|
|
|
// Copyright (C) 2015-2018 David Capello
|
2015-10-20 14:27:05 +00:00
|
|
|
//
|
2016-08-26 20:02:58 +00:00
|
|
|
// This program is distributed under the terms of
|
|
|
|
// the End-User License Agreement for Aseprite.
|
2015-10-20 14:27:05 +00:00
|
|
|
|
|
|
|
#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"
|
2018-07-07 05:47:42 +00:00
|
|
|
#include "app/context_observer.h"
|
2018-07-07 14:54:44 +00:00
|
|
|
#include "app/doc.h"
|
2018-07-07 05:55:27 +00:00
|
|
|
#include "app/doc_undo.h"
|
|
|
|
#include "app/doc_undo_observer.h"
|
2018-07-07 11:38:04 +00:00
|
|
|
#include "app/docs_observer.h"
|
2018-07-15 01:47:03 +00:00
|
|
|
#include "app/doc_access.h"
|
2015-12-11 17:02:42 +00:00
|
|
|
#include "app/modules/gui.h"
|
2015-12-28 19:54:14 +00:00
|
|
|
#include "app/modules/palettes.h"
|
2018-07-07 05:47:42 +00:00
|
|
|
#include "app/site.h"
|
2015-10-20 14:27:05 +00:00
|
|
|
#include "base/bind.h"
|
2016-05-09 21:19:49 +00:00
|
|
|
#include "base/mem_utils.h"
|
2018-06-08 18:52:10 +00:00
|
|
|
#include "ui/listitem.h"
|
|
|
|
#include "ui/message.h"
|
2015-10-20 14:27:05 +00:00
|
|
|
#include "undo/undo_state.h"
|
|
|
|
|
|
|
|
#include "undo_history.xml.h"
|
|
|
|
|
|
|
|
namespace app {
|
|
|
|
|
|
|
|
class UndoHistoryWindow : public app::gen::UndoHistory,
|
2018-07-07 05:47:42 +00:00
|
|
|
public ContextObserver,
|
2018-07-07 11:38:04 +00:00
|
|
|
public DocsObserver,
|
2018-07-07 05:55:27 +00:00
|
|
|
public DocUndoObserver {
|
2015-10-20 14:27:05 +00:00
|
|
|
public:
|
|
|
|
class Item : public ui::ListItem {
|
|
|
|
public:
|
|
|
|
Item(const undo::UndoState* state)
|
|
|
|
: ui::ListItem(
|
2016-05-09 20:49:52 +00:00
|
|
|
(state ?
|
|
|
|
static_cast<Cmd*>(state->cmd())->label()
|
|
|
|
#if _DEBUG
|
2016-05-09 21:19:49 +00:00
|
|
|
+ std::string(" ") + base::get_pretty_memory_size(static_cast<Cmd*>(state->cmd())->memSize())
|
2016-05-09 20:49:52 +00:00
|
|
|
#endif
|
|
|
|
: std::string("Initial State"))),
|
2015-10-20 14:27:05 +00:00
|
|
|
m_state(state) {
|
|
|
|
}
|
|
|
|
const undo::UndoState* state() { return m_state; }
|
|
|
|
private:
|
|
|
|
const undo::UndoState* m_state;
|
|
|
|
};
|
|
|
|
|
|
|
|
UndoHistoryWindow(Context* ctx)
|
2017-10-27 00:42:25 +00:00
|
|
|
: m_ctx(ctx)
|
|
|
|
, m_document(nullptr) {
|
|
|
|
m_title = text();
|
2015-10-20 14:27:05 +00:00
|
|
|
actions()->Change.connect(&UndoHistoryWindow::onChangeAction, this);
|
|
|
|
}
|
|
|
|
|
|
|
|
~UndoHistoryWindow() {
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
bool onProcessMessage(ui::Message* msg) override {
|
|
|
|
switch (msg->type()) {
|
|
|
|
|
|
|
|
case ui::kOpenMessage:
|
2015-12-11 17:02:42 +00:00
|
|
|
load_window_pos(this, "UndoHistory");
|
|
|
|
|
2016-09-13 18:02:00 +00:00
|
|
|
m_ctx->add_observer(this);
|
|
|
|
m_ctx->documents().add_observer(this);
|
2015-10-20 14:27:05 +00:00
|
|
|
if (m_ctx->activeDocument()) {
|
2015-12-28 19:54:14 +00:00
|
|
|
m_frame = m_ctx->activeSite().frame();
|
|
|
|
|
2018-07-07 14:54:44 +00:00
|
|
|
attachDocument(m_ctx->activeDocument());
|
2015-10-20 14:27:05 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case ui::kCloseMessage:
|
2015-12-11 17:02:42 +00:00
|
|
|
save_window_pos(this, "UndoHistory");
|
|
|
|
|
2015-10-20 14:27:05 +00:00
|
|
|
if (m_document)
|
|
|
|
detachDocument();
|
2016-09-13 18:02:00 +00:00
|
|
|
m_ctx->documents().remove_observer(this);
|
|
|
|
m_ctx->remove_observer(this);
|
2015-10-20 14:27:05 +00:00
|
|
|
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 {
|
2018-07-15 01:47:03 +00:00
|
|
|
DocWriter writer(m_document, 100);
|
2015-10-20 14:27:05 +00:00
|
|
|
m_document->undoHistory()->moveToState(item->state());
|
|
|
|
m_document->generateMaskBoundaries();
|
2015-12-28 19:54:14 +00:00
|
|
|
|
|
|
|
// TODO this should be an observer of the current document palette
|
|
|
|
set_current_palette(m_document->sprite()->palette(m_frame),
|
|
|
|
false);
|
|
|
|
|
2015-10-20 14:27:05 +00:00
|
|
|
m_document->notifyGeneralUpdate();
|
|
|
|
}
|
|
|
|
catch (const std::exception& ex) {
|
|
|
|
selectState(m_document->undoHistory()->currentState());
|
|
|
|
Console::showException(ex);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// ContextObserver
|
2018-07-07 05:47:42 +00:00
|
|
|
void onActiveSiteChange(const Site& site) override {
|
2015-12-28 19:54:14 +00:00
|
|
|
m_frame = site.frame();
|
|
|
|
|
2015-10-20 14:27:05 +00:00
|
|
|
if (m_document == site.document())
|
|
|
|
return;
|
|
|
|
|
2018-07-07 14:54:44 +00:00
|
|
|
attachDocument(const_cast<Doc*>(site.document()));
|
2015-10-20 14:27:05 +00:00
|
|
|
}
|
|
|
|
|
2018-07-07 11:38:04 +00:00
|
|
|
// DocsObserver
|
2018-07-07 14:54:44 +00:00
|
|
|
void onRemoveDocument(Doc* doc) override {
|
2015-10-20 14:27:05 +00:00
|
|
|
if (m_document && m_document == doc)
|
|
|
|
detachDocument();
|
|
|
|
}
|
|
|
|
|
2018-07-07 05:55:27 +00:00
|
|
|
// DocUndoObserver
|
|
|
|
void onAddUndoState(DocUndo* history) override {
|
2015-10-20 14:27:05 +00:00
|
|
|
ASSERT(history->currentState());
|
|
|
|
Item* item = new Item(history->currentState());
|
|
|
|
actions()->addChild(item);
|
|
|
|
actions()->layout();
|
|
|
|
view()->updateView();
|
|
|
|
actions()->selectChild(item);
|
|
|
|
}
|
|
|
|
|
2018-07-07 05:55:27 +00:00
|
|
|
void onDeleteUndoState(DocUndo* history,
|
2017-10-25 20:25:23 +00:00
|
|
|
undo::UndoState* state) override {
|
|
|
|
for (auto child : actions()->children()) {
|
|
|
|
Item* item = static_cast<Item*>(child);
|
|
|
|
if (item->state() == state) {
|
|
|
|
actions()->removeChild(item);
|
|
|
|
item->deferDelete();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
actions()->layout();
|
|
|
|
view()->updateView();
|
|
|
|
}
|
|
|
|
|
2018-07-07 05:55:27 +00:00
|
|
|
void onCurrentUndoStateChange(DocUndo* history) override {
|
2015-10-20 14:27:05 +00:00
|
|
|
selectState(history->currentState());
|
|
|
|
}
|
|
|
|
|
2018-07-07 05:55:27 +00:00
|
|
|
void onClearRedo(DocUndo* history) override {
|
2015-10-20 14:27:05 +00:00
|
|
|
refillList(history);
|
|
|
|
}
|
|
|
|
|
2018-07-07 05:55:27 +00:00
|
|
|
void onTotalUndoSizeChange(DocUndo* history) override {
|
2017-10-27 00:42:25 +00:00
|
|
|
updateTitle();
|
|
|
|
}
|
|
|
|
|
2018-07-07 14:54:44 +00:00
|
|
|
void attachDocument(Doc* document) {
|
2015-10-20 14:27:05 +00:00
|
|
|
detachDocument();
|
|
|
|
|
|
|
|
m_document = document;
|
|
|
|
if (!document)
|
|
|
|
return;
|
|
|
|
|
2018-07-07 05:55:27 +00:00
|
|
|
DocUndo* history = m_document->undoHistory();
|
2016-09-13 18:02:00 +00:00
|
|
|
history->add_observer(this);
|
2015-10-20 14:27:05 +00:00
|
|
|
|
|
|
|
refillList(history);
|
2017-10-27 00:42:25 +00:00
|
|
|
updateTitle();
|
2015-10-20 14:27:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void detachDocument() {
|
|
|
|
if (!m_document)
|
|
|
|
return;
|
|
|
|
|
|
|
|
clearList();
|
2016-09-13 18:02:00 +00:00
|
|
|
m_document->undoHistory()->remove_observer(this);
|
2015-10-20 14:27:05 +00:00
|
|
|
m_document = nullptr;
|
2017-10-27 00:42:25 +00:00
|
|
|
updateTitle();
|
2015-10-20 14:27:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void clearList() {
|
|
|
|
ui::Widget* child;
|
Refactor several "getNoun()" getters to "noun()"
This is a work-in-progress to create a consistent API and finally
separate the whole Aseprite base/gfx/ui libs into a reusable C++ library.
Classes:
app::IFileItem, app::AppMenuItem, app::skin::SkinPart,
gfx::Rect, gfx::Border, she::FileDialog,
ui::IButtonIcon, ui::Graphics, ui::Overlay, ui::Widget,
ui::ScrollableViewDelegate, and UI events
2015-12-04 17:39:04 +00:00
|
|
|
while ((child = actions()->firstChild()))
|
2015-10-20 14:27:05 +00:00
|
|
|
delete child;
|
|
|
|
|
|
|
|
actions()->layout();
|
|
|
|
view()->updateView();
|
|
|
|
}
|
|
|
|
|
2018-07-07 05:55:27 +00:00
|
|
|
void refillList(DocUndo* history) {
|
2015-10-20 14:27:05 +00:00
|
|
|
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) {
|
2015-12-03 22:46:13 +00:00
|
|
|
for (auto child : actions()->children()) {
|
2015-10-20 14:27:05 +00:00
|
|
|
Item* item = static_cast<Item*>(child);
|
|
|
|
if (item->state() == state) {
|
|
|
|
actions()->selectChild(item);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-10-27 00:42:25 +00:00
|
|
|
void updateTitle() {
|
|
|
|
if (!m_document)
|
|
|
|
setText(m_title);
|
|
|
|
else
|
|
|
|
setTextf("%s (%s)",
|
|
|
|
m_title.c_str(),
|
|
|
|
base::get_pretty_memory_size(m_document->undoHistory()->totalUndoSize()).c_str());
|
|
|
|
}
|
|
|
|
|
2015-10-20 14:27:05 +00:00
|
|
|
Context* m_ctx;
|
2018-07-07 14:54:44 +00:00
|
|
|
Doc* m_document;
|
2015-12-28 19:54:14 +00:00
|
|
|
doc::frame_t m_frame;
|
2017-10-27 00:42:25 +00:00
|
|
|
std::string m_title;
|
2015-10-20 14:27:05 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
class UndoHistoryCommand : public Command {
|
|
|
|
public:
|
|
|
|
UndoHistoryCommand();
|
|
|
|
|
|
|
|
protected:
|
|
|
|
void onExecute(Context* ctx) override;
|
|
|
|
};
|
|
|
|
|
|
|
|
static UndoHistoryWindow* g_window = NULL;
|
|
|
|
|
|
|
|
UndoHistoryCommand::UndoHistoryCommand()
|
2017-12-01 18:10:21 +00:00
|
|
|
: Command(CommandId::UndoHistory(), CmdUIOnlyFlag)
|
2015-10-20 14:27:05 +00:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
void UndoHistoryCommand::onExecute(Context* ctx)
|
|
|
|
{
|
|
|
|
if (!g_window)
|
|
|
|
g_window = new UndoHistoryWindow(ctx);
|
|
|
|
|
|
|
|
if (g_window->isVisible())
|
2017-08-29 19:23:19 +00:00
|
|
|
g_window->closeWindow(nullptr);
|
2015-10-20 14:27:05 +00:00
|
|
|
else
|
|
|
|
g_window->openWindow();
|
|
|
|
}
|
|
|
|
|
|
|
|
Command* CommandFactory::createUndoHistoryCommand()
|
|
|
|
{
|
|
|
|
return new UndoHistoryCommand;
|
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace app
|