[aseprite] 201/250: Add "Undo History" command (fix #739)

Tobias Hansen thansen at moszumanska.debian.org
Sun Dec 20 15:27:30 UTC 2015


This is an automated email from the git hooks/post-receive script.

thansen pushed a commit to branch master
in repository aseprite.

commit 2eada35a38fe86e705c9a23b377a2e5f87a14f8c
Author: David Capello <davidcapello at gmail.com>
Date:   Tue Oct 20 11:27:05 2015 -0300

    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.
---
 data/widgets/undo_history.xml         |   9 ++
 src/app/CMakeLists.txt                |   1 +
 src/app/commands/cmd_undo_history.cpp | 239 ++++++++++++++++++++++++++++++++++
 src/app/commands/commands_list.h      |   1 +
 src/app/document_undo.cpp             |  18 ++-
 src/app/document_undo.h               |   9 +-
 src/app/document_undo_observer.h      |  31 +++++
 src/undo/undo_history.cpp             |  17 +--
 src/undo/undo_history.h               |   9 +-
 9 files changed, 318 insertions(+), 16 deletions(-)

diff --git a/data/widgets/undo_history.xml b/data/widgets/undo_history.xml
new file mode 100644
index 0000000..3f0328d
--- /dev/null
+++ b/data/widgets/undo_history.xml
@@ -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>
diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt
index 30e1b09..e1d1723 100644
--- a/src/app/CMakeLists.txt
+++ b/src/app/CMakeLists.txt
@@ -252,6 +252,7 @@ add_library(app-lib
   commands/cmd_timeline.cpp
   commands/cmd_toggle_preview.cpp
   commands/cmd_undo.cpp
+  commands/cmd_undo_history.cpp
   commands/cmd_unlink_cel.cpp
   commands/cmd_zoom.cpp
   commands/command.cpp
diff --git a/src/app/commands/cmd_undo_history.cpp b/src/app/commands/cmd_undo_history.cpp
new file mode 100644
index 0000000..a6ce094
--- /dev/null
+++ b/src/app/commands/cmd_undo_history.cpp
@@ -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
diff --git a/src/app/commands/commands_list.h b/src/app/commands/commands_list.h
index 0426326..c7120c2 100644
--- a/src/app/commands/commands_list.h
+++ b/src/app/commands/commands_list.h
@@ -118,5 +118,6 @@ FOR_EACH_COMMAND(TiledMode)
 FOR_EACH_COMMAND(Timeline)
 FOR_EACH_COMMAND(TogglePreview)
 FOR_EACH_COMMAND(Undo)
+FOR_EACH_COMMAND(UndoHistory)
 FOR_EACH_COMMAND(UnlinkCel)
 FOR_EACH_COMMAND(Zoom)
diff --git a/src/app/document_undo.cpp b/src/app/document_undo.cpp
index 90ef948..a979cd7 100644
--- a/src/app/document_undo.cpp
+++ b/src/app/document_undo.cpp
@@ -14,6 +14,7 @@
 #include "app/app.h"
 #include "app/cmd.h"
 #include "app/cmd_transaction.h"
+#include "app/document_undo_observer.h"
 #include "app/pref/preferences.h"
 #include "doc/context.h"
 #include "undo/undo_history.h"
@@ -43,10 +44,11 @@ void DocumentUndo::add(CmdTransaction* cmd)
   // A linear undo history is the default behavior
   if (!App::instance() ||
       !App::instance()->preferences().undo.allowNonlinearHistory()) {
-    m_undoHistory.clearRedo();
+    clearRedo();
   }
 
   m_undoHistory.add(cmd);
+  notifyObservers(&DocumentUndoObserver::onAddUndoState, this);
 }
 
 bool DocumentUndo::canUndo() const
@@ -61,17 +63,20 @@ bool DocumentUndo::canRedo() const
 
 void DocumentUndo::undo()
 {
-  return m_undoHistory.undo();
+  m_undoHistory.undo();
+  notifyObservers(&DocumentUndoObserver::onAfterUndo, this);
 }
 
 void DocumentUndo::redo()
 {
-  return m_undoHistory.redo();
+  m_undoHistory.redo();
+  notifyObservers(&DocumentUndoObserver::onAfterRedo, this);
 }
 
 void DocumentUndo::clearRedo()
 {
-  return m_undoHistory.clearRedo();
+  m_undoHistory.clearRedo();
+  notifyObservers(&DocumentUndoObserver::onClearRedo, this);
 }
 
 bool DocumentUndo::isSavedState() const
@@ -137,6 +142,11 @@ Cmd* DocumentUndo::lastExecutedCmd() const
     return NULL;
 }
 
+void DocumentUndo::moveToState(const undo::UndoState* state)
+{
+  m_undoHistory.moveTo(state);
+}
+
 const undo::UndoState* DocumentUndo::nextUndo() const
 {
   return m_undoHistory.currentState();
diff --git a/src/app/document_undo.h b/src/app/document_undo.h
index 647cc83..7be2ed3 100644
--- a/src/app/document_undo.h
+++ b/src/app/document_undo.h
@@ -10,6 +10,7 @@
 #pragma once
 
 #include "base/disable_copying.h"
+#include "base/observable.h"
 #include "base/unique_ptr.h"
 #include "doc/sprite_position.h"
 #include "undo/undo_history.h"
@@ -25,8 +26,9 @@ namespace app {
 
   class Cmd;
   class CmdTransaction;
+  class DocumentUndoObserver;
 
-  class DocumentUndo {
+  class DocumentUndo : public base::Observable<DocumentUndoObserver> {
   public:
     DocumentUndo();
 
@@ -55,6 +57,11 @@ namespace app {
 
     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:
     const undo::UndoState* nextUndo() const;
     const undo::UndoState* nextRedo() const;
diff --git a/src/app/document_undo_observer.h b/src/app/document_undo_observer.h
new file mode 100644
index 0000000..d466d8d
--- /dev/null
+++ b/src/app/document_undo_observer.h
@@ -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
diff --git a/src/undo/undo_history.cpp b/src/undo/undo_history.cpp
index 90bfbfa..0d34203 100644
--- a/src/undo/undo_history.cpp
+++ b/src/undo/undo_history.cpp
@@ -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;
-  UndoState* pB = b;
+  const UndoState* pA = a;
+  const UndoState* pB = b;
 
   if (pA == nullptr || pB == nullptr)
     return nullptr;
@@ -119,9 +120,9 @@ UndoState* UndoHistory::findCommonParent(UndoState* a, UndoState* b)
   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) {
     while (m_cur != common) {
@@ -131,8 +132,8 @@ void UndoHistory::moveTo(UndoState* new_state)
   }
 
   if (new_state) {
-    std::stack<UndoState*> redo_parents;
-    UndoState* p = new_state;
+    std::stack<const UndoState*> redo_parents;
+    const UndoState* p = new_state;
     while (p != common) {
       redo_parents.push(p);
       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
diff --git a/src/undo/undo_history.h b/src/undo/undo_history.h
index 0880a8e..53d1962 100644
--- a/src/undo/undo_history.h
+++ b/src/undo/undo_history.h
@@ -27,12 +27,15 @@ namespace undo {
     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:
-    UndoState* findCommonParent(UndoState* a, UndoState* b);
-    void moveTo(UndoState* new_state);
+    const UndoState* findCommonParent(const UndoState* a,
+                                      const UndoState* b);
 
     UndoState* m_first;
     UndoState* m_last;

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-games/aseprite.git



More information about the Pkg-games-commits mailing list