aseprite/src/app/commands/cmd_undo_history.cpp
cebolan e773f0697e Fix bug when closing Undo History with a keyboard shortcut
Closes #1423

The bug was happening because when the Undo History window was
closed using the keyboard shortcut, the message kCloseMessage
was never being sent to the window. This is probably not the
only cause, but making UndoHistoryCommand close the window instead
of just hiding it solves the problem.
2017-08-29 15:23:19 -04:00

260 lines
6.2 KiB
C++

// Aseprite
// Copyright (C) 2015-2016 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#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 "app/modules/gui.h"
#include "app/modules/palettes.h"
#include "base/bind.h"
#include "base/mem_utils.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()
#if _DEBUG
+ std::string(" ") + base::get_pretty_memory_size(static_cast<Cmd*>(state->cmd())->memSize())
#endif
: 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:
load_window_pos(this, "UndoHistory");
m_ctx->add_observer(this);
m_ctx->documents().add_observer(this);
if (m_ctx->activeDocument()) {
m_frame = m_ctx->activeSite().frame();
attachDocument(
static_cast<app::Document*>(m_ctx->activeDocument()));
}
break;
case ui::kCloseMessage:
save_window_pos(this, "UndoHistory");
if (m_document)
detachDocument();
m_ctx->documents().remove_observer(this);
m_ctx->remove_observer(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();
// TODO this should be an observer of the current document palette
set_current_palette(m_document->sprite()->palette(m_frame),
false);
m_document->notifyGeneralUpdate();
}
catch (const std::exception& ex) {
selectState(m_document->undoHistory()->currentState());
Console::showException(ex);
}
}
}
// ContextObserver
void onActiveSiteChange(const doc::Site& site) override {
m_frame = site.frame();
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->add_observer(this);
refillList(history);
}
void detachDocument() {
if (!m_document)
return;
clearList();
m_document->undoHistory()->remove_observer(this);
m_document = nullptr;
}
void clearList() {
ui::Widget* child;
while ((child = actions()->firstChild()))
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()->children()) {
Item* item = static_cast<Item*>(child);
if (item->state() == state) {
actions()->selectChild(item);
break;
}
}
}
Context* m_ctx;
app::Document* m_document;
doc::frame_t m_frame;
};
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->closeWindow(nullptr);
else
g_window->openWindow();
}
Command* CommandFactory::createUndoHistoryCommand()
{
return new UndoHistoryCommand;
}
} // namespace app