diff options
author | sdefresne <sdefresne@chromium.org> | 2015-04-17 14:12:18 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2015-04-17 21:14:10 +0000 |
commit | c083d1f4de8537ee81131ddb5d388f2f686fac85 (patch) | |
tree | 91b5c0d53aa666aa08586d0827d2be4c36d240e5 /components | |
parent | 298d7eb01ab6f9c1f8781a31c2fddc13f9e4e4e2 (diff) | |
download | chromium_src-c083d1f4de8537ee81131ddb5d388f2f686fac85.zip chromium_src-c083d1f4de8537ee81131ddb5d388f2f686fac85.tar.gz chromium_src-c083d1f4de8537ee81131ddb5d388f2f686fac85.tar.bz2 |
Move undo files into //components/undo
Create a new component //components/undo and move files from
//chrome/browser/undo into the component.
Add DEPS, OWNERS files and add dependencies on the new component into
chrome/browser/BUILD.gn & chrome/chrome_browser.gypi.
Files where moved using tools/git/move_source_files.py and the #include
lines updated automatically by the script.
BUG=476921
Review URL: https://codereview.chromium.org/1090993003
Cr-Commit-Position: refs/heads/master@{#325718}
Diffstat (limited to 'components')
-rw-r--r-- | components/BUILD.gn | 2 | ||||
-rw-r--r-- | components/OWNERS | 3 | ||||
-rw-r--r-- | components/components.gyp | 1 | ||||
-rw-r--r-- | components/components_tests.gyp | 6 | ||||
-rw-r--r-- | components/undo.gypi | 34 | ||||
-rw-r--r-- | components/undo/BUILD.gn | 39 | ||||
-rw-r--r-- | components/undo/DEPS | 17 | ||||
-rw-r--r-- | components/undo/OWNERS | 5 | ||||
-rw-r--r-- | components/undo/bookmark_renumber_observer.h | 20 | ||||
-rw-r--r-- | components/undo/bookmark_undo_service.cc | 495 | ||||
-rw-r--r-- | components/undo/bookmark_undo_service.h | 80 | ||||
-rw-r--r-- | components/undo/bookmark_undo_service_test.cc | 421 | ||||
-rw-r--r-- | components/undo/bookmark_undo_utils.cc | 24 | ||||
-rw-r--r-- | components/undo/bookmark_undo_utils.h | 29 | ||||
-rw-r--r-- | components/undo/undo_manager.cc | 224 | ||||
-rw-r--r-- | components/undo/undo_manager.h | 134 | ||||
-rw-r--r-- | components/undo/undo_manager_observer.h | 18 | ||||
-rw-r--r-- | components/undo/undo_manager_test.cc | 265 | ||||
-rw-r--r-- | components/undo/undo_operation.h | 26 |
19 files changed, 1843 insertions, 0 deletions
diff --git a/components/BUILD.gn b/components/BUILD.gn index 2b58643..62c2946 100644 --- a/components/BUILD.gn +++ b/components/BUILD.gn @@ -95,6 +95,7 @@ group("all_components") { "//components/translate/core/browser", "//components/translate/core/common", "//components/ui/zoom:ui_zoom", + "//components/undo", "//components/update_client", "//components/url_fixer", "//components/url_matcher", @@ -267,6 +268,7 @@ test("components_unittests") { "//components/omnibox:unit_tests", "//components/ownership:unit_tests", "//components/packed_ct_ev_whitelist:unit_tests", + "//components/undo:unit_tests", "//components/update_client:unit_tests", "//components/variations:unit_tests", "//components/web_resource:unit_tests", diff --git a/components/OWNERS b/components/OWNERS index 869139d..ef674a0 100644 --- a/components/OWNERS +++ b/components/OWNERS @@ -256,10 +256,13 @@ per-file translate_strings.grdp=toyoshim@chromium.org per-file startup_metric_utils.gypi=jeremy@chromium.org +per-file undo.gypi=sky@chromium.org +per-file undo.gypi=tom.cassiotis@gmail.com per-file undo_strings.grdp=sky@chromium.org per-file undo_strings.grdp=tom.cassiotis@gmail.com # For refactoring only during componentization of //chrome/browser/undo. +per-file undo.gypi=sdefresne@chromium.org per-file undo_strings.grdp=sdefresne@chromium.org per-file user_manager.gypi=antrim@chromium.org diff --git a/components/components.gyp b/components/components.gyp index 67988d1..d6c5e0a8 100644 --- a/components/components.gyp +++ b/components/components.gyp @@ -65,6 +65,7 @@ 'sync_driver.gypi', 'translate.gypi', 'ui_zoom.gypi', + 'undo.gypi', 'update_client.gypi', 'url_fixer.gypi', 'url_matcher.gypi', diff --git a/components/components_tests.gyp b/components/components_tests.gyp index ad976a6..39b57a2 100644 --- a/components/components_tests.gyp +++ b/components/components_tests.gyp @@ -162,6 +162,10 @@ 'favicon/content/content_favicon_driver_unittest.cc', 'favicon/core/favicon_handler_unittest.cc', ], + 'undo_unittest_sources': [ + 'undo/bookmark_undo_service_test.cc', + 'undo/undo_manager_test.cc', + ], # Note: GN tests converted to here, need to do the rest. 'audio_modem_unittest_sources': [ @@ -649,6 +653,7 @@ '<@(suggestions_unittest_sources)', '<@(sync_driver_unittest_sources)', '<@(translate_unittest_sources)', + '<@(undo_unittest_sources)', '<@(update_client_unittest_sources)', '<@(url_fixer_unittest_sources)', '<@(url_matcher_unittest_sources)', @@ -759,6 +764,7 @@ 'components.gyp:translate_core_common', 'components.gyp:translate_core_language_detection', 'components.gyp:ui_zoom', + 'components.gyp:undo_component', 'components.gyp:update_client', 'components.gyp:update_client_test_support', 'components.gyp:url_fixer', diff --git a/components/undo.gypi b/components/undo.gypi new file mode 100644 index 0000000..7039ace --- /dev/null +++ b/components/undo.gypi @@ -0,0 +1,34 @@ +# Copyright 2015 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +{ + 'targets': [ + { + # GN version: //components/undo + 'target_name': 'undo_component', + 'type': 'static_library', + 'include_dirs': [ + '..', + ], + 'dependencies': [ + '../base/base.gyp:base', + '../ui/base/ui_base.gyp:ui_base', + 'bookmarks_browser', + 'components_strings.gyp:components_strings', + 'keyed_service_core', + ], + 'sources': [ + 'undo/bookmark_renumber_observer.h', + 'undo/bookmark_undo_service.cc', + 'undo/bookmark_undo_service.h', + 'undo/bookmark_undo_utils.cc', + 'undo/bookmark_undo_utils.h', + 'undo/undo_manager.cc', + 'undo/undo_manager.h', + 'undo/undo_manager_observer.h', + 'undo/undo_operation.h', + ], + }, + ], +} diff --git a/components/undo/BUILD.gn b/components/undo/BUILD.gn new file mode 100644 index 0000000..c891cc0 --- /dev/null +++ b/components/undo/BUILD.gn @@ -0,0 +1,39 @@ +# Copyright 2015 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +static_library("undo") { + sources = [ + "bookmark_renumber_observer.h", + "bookmark_undo_service.cc", + "bookmark_undo_service.h", + "bookmark_undo_utils.cc", + "bookmark_undo_utils.h", + "undo_manager.cc", + "undo_manager.h", + "undo_manager_observer.h", + "undo_operation.h", + ] + + deps = [ + "//base", + "//components/bookmarks/browser", + "//components/keyed_service/core", + "//components/strings", + "//ui/base", + ] +} + +source_set("unit_tests") { + testonly = true + sources = [ + "bookmark_undo_service_test.cc", + "undo_manager_test.cc", + ] + + deps = [ + "//components/bookmarks/test", + "//components/undo", + "//testing/gtest", + ] +} diff --git a/components/undo/DEPS b/components/undo/DEPS new file mode 100644 index 0000000..a70c7a8 --- /dev/null +++ b/components/undo/DEPS @@ -0,0 +1,17 @@ +include_rules = [ + # undo component is used on iOS and desktop and thus must not depends on + # system-specific directories. + "-content", + "-ios", + + "+components/bookmarks/browser", + "+components/keyed_service/core", + "+grit/components_strings.h", + "+ui/base", +] + +specific_include_rules = { + ".*_test\.cc": [ + "+components/bookmarks/test", + ], +} diff --git a/components/undo/OWNERS b/components/undo/OWNERS new file mode 100644 index 0000000..08fd84c --- /dev/null +++ b/components/undo/OWNERS @@ -0,0 +1,5 @@ +sky@chromium.org +tom.cassiotis@gmail.com + +# For refactoring only during componentization of //chrome/browser/undo. +sdefresne@chromium.org diff --git a/components/undo/bookmark_renumber_observer.h b/components/undo/bookmark_renumber_observer.h new file mode 100644 index 0000000..4dc8df0 --- /dev/null +++ b/components/undo/bookmark_renumber_observer.h @@ -0,0 +1,20 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_UNDO_BOOKMARK_RENUMBER_OBSERVER_H_ +#define COMPONENTS_UNDO_BOOKMARK_RENUMBER_OBSERVER_H_ + +#include "base/basictypes.h" + +class BookmarkRenumberObserver { + public: + // Invoked when a bookmark id has been renumbered so that any + // BookmarkUndoOperations that refer to that bookmark can be updated. + virtual void OnBookmarkRenumbered(int64 old_id, int64 new_id) = 0; + + protected: + virtual ~BookmarkRenumberObserver() {} +}; + +#endif // COMPONENTS_UNDO_BOOKMARK_RENUMBER_OBSERVER_H_ diff --git a/components/undo/bookmark_undo_service.cc b/components/undo/bookmark_undo_service.cc new file mode 100644 index 0000000..695bacb --- /dev/null +++ b/components/undo/bookmark_undo_service.cc @@ -0,0 +1,495 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/undo/bookmark_undo_service.h" + +#include "components/bookmarks/browser/bookmark_model.h" +#include "components/bookmarks/browser/bookmark_node_data.h" +#include "components/bookmarks/browser/bookmark_utils.h" +#include "components/bookmarks/browser/scoped_group_bookmark_actions.h" +#include "components/undo/bookmark_renumber_observer.h" +#include "components/undo/undo_operation.h" +#include "grit/components_strings.h" + +using bookmarks::BookmarkModel; +using bookmarks::BookmarkNode; +using bookmarks::BookmarkNodeData; + +namespace { + +// BookmarkUndoOperation ------------------------------------------------------ + +// Base class for all bookmark related UndoOperations that facilitates access to +// the BookmarkUndoService. +class BookmarkUndoOperation : public UndoOperation, + public BookmarkRenumberObserver { + public: + BookmarkUndoOperation(BookmarkModel* bookmark_model, + BookmarkRenumberObserver* undo_renumber_observer) + : bookmark_model_(bookmark_model), + undo_renumber_observer_(undo_renumber_observer) {} + ~BookmarkUndoOperation() override {} + + BookmarkModel* bookmark_model() { return bookmark_model_; } + + BookmarkRenumberObserver* undo_renumber_observer() { + return undo_renumber_observer_; + } + + private: + BookmarkModel* bookmark_model_; + BookmarkRenumberObserver* undo_renumber_observer_; +}; + +// BookmarkAddOperation ------------------------------------------------------- + +// Handles the undo of the insertion of a bookmark or folder. +class BookmarkAddOperation : public BookmarkUndoOperation { + public: + BookmarkAddOperation(BookmarkModel* bookmark_model, + BookmarkRenumberObserver* undo_renumber_observer, + const BookmarkNode* parent, + int index); + ~BookmarkAddOperation() override {} + + // UndoOperation: + void Undo() override; + int GetUndoLabelId() const override; + int GetRedoLabelId() const override; + + // BookmarkRenumberObserver: + void OnBookmarkRenumbered(int64 old_id, int64 new_id) override; + + private: + int64 parent_id_; + const int index_; + + DISALLOW_COPY_AND_ASSIGN(BookmarkAddOperation); +}; + +BookmarkAddOperation::BookmarkAddOperation( + BookmarkModel* bookmark_model, + BookmarkRenumberObserver* undo_renumber_observer, + const BookmarkNode* parent, + int index) + : BookmarkUndoOperation(bookmark_model, undo_renumber_observer), + parent_id_(parent->id()), + index_(index) { +} + +void BookmarkAddOperation::Undo() { + BookmarkModel* model = bookmark_model(); + const BookmarkNode* parent = + bookmarks::GetBookmarkNodeByID(model, parent_id_); + DCHECK(parent); + + model->Remove(parent, index_); +} + +int BookmarkAddOperation::GetUndoLabelId() const { + return IDS_BOOKMARK_BAR_UNDO_ADD; +} + +int BookmarkAddOperation::GetRedoLabelId() const { + return IDS_BOOKMARK_BAR_REDO_DELETE; +} + +void BookmarkAddOperation::OnBookmarkRenumbered(int64 old_id, int64 new_id) { + if (parent_id_ == old_id) + parent_id_ = new_id; +} + +// BookmarkRemoveOperation ---------------------------------------------------- + +// Handles the undo of the deletion of a bookmark node. For a bookmark folder, +// the information for all descendant bookmark nodes is maintained. +// +// The BookmarkModel allows only single bookmark node to be removed. +class BookmarkRemoveOperation : public BookmarkUndoOperation { + public: + BookmarkRemoveOperation(BookmarkModel* bookmark_model, + BookmarkRenumberObserver* undo_renumber_observer, + const BookmarkNode* parent, + int old_index, + const BookmarkNode* node); + ~BookmarkRemoveOperation() override {} + + // UndoOperation: + void Undo() override; + int GetUndoLabelId() const override; + int GetRedoLabelId() const override; + + // BookmarkRenumberObserver: + void OnBookmarkRenumbered(int64 old_id, int64 new_id) override; + + private: + void UpdateBookmarkIds(const BookmarkNodeData::Element& element, + const BookmarkNode* parent, + int index_added_at); + + int64 parent_id_; + const int old_index_; + BookmarkNodeData removed_node_; + + DISALLOW_COPY_AND_ASSIGN(BookmarkRemoveOperation); +}; + +BookmarkRemoveOperation::BookmarkRemoveOperation( + BookmarkModel* bookmark_model, + BookmarkRenumberObserver* undo_renumber_observer, + const BookmarkNode* parent, + int old_index, + const BookmarkNode* node) + : BookmarkUndoOperation(bookmark_model, undo_renumber_observer), + parent_id_(parent->id()), + old_index_(old_index), + removed_node_(node) { +} + +void BookmarkRemoveOperation::Undo() { + DCHECK(removed_node_.is_valid()); + BookmarkModel* model = bookmark_model(); + const BookmarkNode* parent = + bookmarks::GetBookmarkNodeByID(model, parent_id_); + DCHECK(parent); + + bookmarks::CloneBookmarkNode( + model, removed_node_.elements, parent, old_index_, false); + UpdateBookmarkIds(removed_node_.elements[0], parent, old_index_); +} + +int BookmarkRemoveOperation::GetUndoLabelId() const { + return IDS_BOOKMARK_BAR_UNDO_DELETE; +} + +int BookmarkRemoveOperation::GetRedoLabelId() const { + return IDS_BOOKMARK_BAR_REDO_ADD; +} + +void BookmarkRemoveOperation::UpdateBookmarkIds( + const BookmarkNodeData::Element& element, + const BookmarkNode* parent, + int index_added_at) { + const BookmarkNode* node = parent->GetChild(index_added_at); + if (element.id() != node->id()) + undo_renumber_observer()->OnBookmarkRenumbered(element.id(), node->id()); + if (!element.is_url) { + for (int i = 0; i < static_cast<int>(element.children.size()); ++i) + UpdateBookmarkIds(element.children[i], node, 0); + } +} + +void BookmarkRemoveOperation::OnBookmarkRenumbered(int64 old_id, int64 new_id) { + if (parent_id_ == old_id) + parent_id_ = new_id; +} + +// BookmarkEditOperation ------------------------------------------------------ + +// Handles the undo of the modification of a bookmark node. +class BookmarkEditOperation : public BookmarkUndoOperation { + public: + BookmarkEditOperation(BookmarkModel* bookmark_model, + BookmarkRenumberObserver* undo_renumber_observer, + const BookmarkNode* node); + ~BookmarkEditOperation() override {} + + // UndoOperation: + void Undo() override; + int GetUndoLabelId() const override; + int GetRedoLabelId() const override; + + // BookmarkRenumberObserver: + void OnBookmarkRenumbered(int64 old_id, int64 new_id) override; + + private: + int64 node_id_; + BookmarkNodeData original_bookmark_; + + DISALLOW_COPY_AND_ASSIGN(BookmarkEditOperation); +}; + +BookmarkEditOperation::BookmarkEditOperation( + BookmarkModel* bookmark_model, + BookmarkRenumberObserver* undo_renumber_observer, + const BookmarkNode* node) + : BookmarkUndoOperation(bookmark_model, undo_renumber_observer), + node_id_(node->id()), + original_bookmark_(node) { +} + +void BookmarkEditOperation::Undo() { + DCHECK(original_bookmark_.is_valid()); + BookmarkModel* model = bookmark_model(); + const BookmarkNode* node = bookmarks::GetBookmarkNodeByID(model, node_id_); + DCHECK(node); + + model->SetTitle(node, original_bookmark_.elements[0].title); + if (original_bookmark_.elements[0].is_url) + model->SetURL(node, original_bookmark_.elements[0].url); +} + +int BookmarkEditOperation::GetUndoLabelId() const { + return IDS_BOOKMARK_BAR_UNDO_EDIT; +} + +int BookmarkEditOperation::GetRedoLabelId() const { + return IDS_BOOKMARK_BAR_REDO_EDIT; +} + +void BookmarkEditOperation::OnBookmarkRenumbered(int64 old_id, int64 new_id) { + if (node_id_ == old_id) + node_id_ = new_id; +} + +// BookmarkMoveOperation ------------------------------------------------------ + +// Handles the undo of a bookmark being moved to a new location. +class BookmarkMoveOperation : public BookmarkUndoOperation { + public: + BookmarkMoveOperation(BookmarkModel* bookmark_model, + BookmarkRenumberObserver* undo_renumber_observer, + const BookmarkNode* old_parent, + int old_index, + const BookmarkNode* new_parent, + int new_index); + ~BookmarkMoveOperation() override {} + int GetUndoLabelId() const override; + int GetRedoLabelId() const override; + + // UndoOperation: + void Undo() override; + + // BookmarkRenumberObserver: + void OnBookmarkRenumbered(int64 old_id, int64 new_id) override; + + private: + int64 old_parent_id_; + int64 new_parent_id_; + int old_index_; + int new_index_; + + DISALLOW_COPY_AND_ASSIGN(BookmarkMoveOperation); +}; + +BookmarkMoveOperation::BookmarkMoveOperation( + BookmarkModel* bookmark_model, + BookmarkRenumberObserver* undo_renumber_observer, + const BookmarkNode* old_parent, + int old_index, + const BookmarkNode* new_parent, + int new_index) + : BookmarkUndoOperation(bookmark_model, undo_renumber_observer), + old_parent_id_(old_parent->id()), + new_parent_id_(new_parent->id()), + old_index_(old_index), + new_index_(new_index) { +} + +void BookmarkMoveOperation::Undo() { + BookmarkModel* model = bookmark_model(); + const BookmarkNode* old_parent = + bookmarks::GetBookmarkNodeByID(model, old_parent_id_); + const BookmarkNode* new_parent = + bookmarks::GetBookmarkNodeByID(model, new_parent_id_); + DCHECK(old_parent); + DCHECK(new_parent); + + const BookmarkNode* node = new_parent->GetChild(new_index_); + int destination_index = old_index_; + + // If the bookmark was moved up within the same parent then the destination + // index needs to be incremented since the old index did not account for the + // moved bookmark. + if (old_parent == new_parent && new_index_ < old_index_) + ++destination_index; + + model->Move(node, old_parent, destination_index); +} + +int BookmarkMoveOperation::GetUndoLabelId() const { + return IDS_BOOKMARK_BAR_UNDO_MOVE; +} + +int BookmarkMoveOperation::GetRedoLabelId() const { + return IDS_BOOKMARK_BAR_REDO_MOVE; +} + +void BookmarkMoveOperation::OnBookmarkRenumbered(int64 old_id, int64 new_id) { + if (old_parent_id_ == old_id) + old_parent_id_ = new_id; + if (new_parent_id_ == old_id) + new_parent_id_ = new_id; +} + +// BookmarkReorderOperation --------------------------------------------------- + +// Handle the undo of reordering of bookmarks that can happen as a result of +// sorting a bookmark folder by name or the undo of that operation. The change +// of order is not recursive so only the order of the immediate children of the +// folder need to be restored. +class BookmarkReorderOperation : public BookmarkUndoOperation { + public: + BookmarkReorderOperation(BookmarkModel* bookmark_model, + BookmarkRenumberObserver* undo_renumber_observer, + const BookmarkNode* parent); + ~BookmarkReorderOperation() override; + + // UndoOperation: + void Undo() override; + int GetUndoLabelId() const override; + int GetRedoLabelId() const override; + + // BookmarkRenumberObserver: + void OnBookmarkRenumbered(int64 old_id, int64 new_id) override; + + private: + int64 parent_id_; + std::vector<int64> ordered_bookmarks_; + + DISALLOW_COPY_AND_ASSIGN(BookmarkReorderOperation); +}; + +BookmarkReorderOperation::BookmarkReorderOperation( + BookmarkModel* bookmark_model, + BookmarkRenumberObserver* undo_renumber_observer, + const BookmarkNode* parent) + : BookmarkUndoOperation(bookmark_model, undo_renumber_observer), + parent_id_(parent->id()) { + ordered_bookmarks_.resize(parent->child_count()); + for (int i = 0; i < parent->child_count(); ++i) + ordered_bookmarks_[i] = parent->GetChild(i)->id(); +} + +BookmarkReorderOperation::~BookmarkReorderOperation() { +} + +void BookmarkReorderOperation::Undo() { + BookmarkModel* model = bookmark_model(); + const BookmarkNode* parent = + bookmarks::GetBookmarkNodeByID(model, parent_id_); + DCHECK(parent); + + std::vector<const BookmarkNode*> ordered_nodes; + for (size_t i = 0; i < ordered_bookmarks_.size(); ++i) { + ordered_nodes.push_back( + bookmarks::GetBookmarkNodeByID(model, ordered_bookmarks_[i])); + } + + model->ReorderChildren(parent, ordered_nodes); +} + +int BookmarkReorderOperation::GetUndoLabelId() const { + return IDS_BOOKMARK_BAR_UNDO_REORDER; +} + +int BookmarkReorderOperation::GetRedoLabelId() const { + return IDS_BOOKMARK_BAR_REDO_REORDER; +} + +void BookmarkReorderOperation::OnBookmarkRenumbered(int64 old_id, + int64 new_id) { + if (parent_id_ == old_id) + parent_id_ = new_id; + for (size_t i = 0; i < ordered_bookmarks_.size(); ++i) { + if (ordered_bookmarks_[i] == old_id) + ordered_bookmarks_[i] = new_id; + } +} + +} // namespace + +// BookmarkUndoService -------------------------------------------------------- + +BookmarkUndoService::BookmarkUndoService() : scoped_observer_(this) { +} + +BookmarkUndoService::~BookmarkUndoService() { +} + +void BookmarkUndoService::Start(BookmarkModel* model) { + scoped_observer_.Add(model); +} + +void BookmarkUndoService::Shutdown() { + scoped_observer_.RemoveAll(); +} + +void BookmarkUndoService::BookmarkModelLoaded(BookmarkModel* model, + bool ids_reassigned) { + undo_manager_.RemoveAllOperations(); +} + +void BookmarkUndoService::BookmarkModelBeingDeleted(BookmarkModel* model) { + undo_manager_.RemoveAllOperations(); +} + +void BookmarkUndoService::BookmarkNodeMoved(BookmarkModel* model, + const BookmarkNode* old_parent, + int old_index, + const BookmarkNode* new_parent, + int new_index) { + scoped_ptr<UndoOperation> op(new BookmarkMoveOperation( + model, this, old_parent, old_index, new_parent, new_index)); + undo_manager()->AddUndoOperation(op.Pass()); +} + +void BookmarkUndoService::BookmarkNodeAdded(BookmarkModel* model, + const BookmarkNode* parent, + int index) { + scoped_ptr<UndoOperation> op( + new BookmarkAddOperation(model, this, parent, index)); + undo_manager()->AddUndoOperation(op.Pass()); +} + +void BookmarkUndoService::OnWillRemoveBookmarks(BookmarkModel* model, + const BookmarkNode* parent, + int old_index, + const BookmarkNode* node) { + scoped_ptr<UndoOperation> op( + new BookmarkRemoveOperation(model, this, parent, old_index, node)); + undo_manager()->AddUndoOperation(op.Pass()); +} + +void BookmarkUndoService::OnWillRemoveAllUserBookmarks(BookmarkModel* model) { + bookmarks::ScopedGroupBookmarkActions merge_removes(model); + for (int i = 0; i < model->root_node()->child_count(); ++i) { + const BookmarkNode* permanent_node = model->root_node()->GetChild(i); + for (int j = permanent_node->child_count() - 1; j >= 0; --j) { + scoped_ptr<UndoOperation> op(new BookmarkRemoveOperation( + model, this, permanent_node, j, permanent_node->GetChild(j))); + undo_manager()->AddUndoOperation(op.Pass()); + } + } +} + +void BookmarkUndoService::OnWillChangeBookmarkNode(BookmarkModel* model, + const BookmarkNode* node) { + scoped_ptr<UndoOperation> op(new BookmarkEditOperation(model, this, node)); + undo_manager()->AddUndoOperation(op.Pass()); +} + +void BookmarkUndoService::OnWillReorderBookmarkNode(BookmarkModel* model, + const BookmarkNode* node) { + scoped_ptr<UndoOperation> op(new BookmarkReorderOperation(model, this, node)); + undo_manager()->AddUndoOperation(op.Pass()); +} + +void BookmarkUndoService::GroupedBookmarkChangesBeginning( + BookmarkModel* model) { + undo_manager()->StartGroupingActions(); +} + +void BookmarkUndoService::GroupedBookmarkChangesEnded(BookmarkModel* model) { + undo_manager()->EndGroupingActions(); +} + +void BookmarkUndoService::OnBookmarkRenumbered(int64 old_id, int64 new_id) { + std::vector<UndoOperation*> all_operations = + undo_manager()->GetAllUndoOperations(); + for (UndoOperation* op : all_operations) { + static_cast<BookmarkUndoOperation*>(op) + ->OnBookmarkRenumbered(old_id, new_id); + } +} diff --git a/components/undo/bookmark_undo_service.h b/components/undo/bookmark_undo_service.h new file mode 100644 index 0000000..8ae2eaf --- /dev/null +++ b/components/undo/bookmark_undo_service.h @@ -0,0 +1,80 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_UNDO_BOOKMARK_UNDO_SERVICE_H_ +#define COMPONENTS_UNDO_BOOKMARK_UNDO_SERVICE_H_ + +#include <map> + +#include "base/scoped_observer.h" +#include "components/bookmarks/browser/base_bookmark_model_observer.h" +#include "components/bookmarks/browser/bookmark_node_data.h" +#include "components/keyed_service/core/keyed_service.h" +#include "components/undo/bookmark_renumber_observer.h" +#include "components/undo/undo_manager.h" + +namespace bookmarks { +class BookmarkModel; +class BookmarkModelObserver; +} + +// BookmarkUndoService -------------------------------------------------------- + +// BookmarkUndoService is owned by the profile, and is responsible for observing +// BookmarkModel changes in order to provide an undo for those changes. +class BookmarkUndoService : public bookmarks::BaseBookmarkModelObserver, + public KeyedService, + public BookmarkRenumberObserver { + public: + BookmarkUndoService(); + ~BookmarkUndoService() override; + + // Starts the BookmarkUndoService and register it as a BookmarkModelObserver. + // Calling this method is optional, but the service will be inactive until it + // is called. + void Start(bookmarks::BookmarkModel* model); + + UndoManager* undo_manager() { return &undo_manager_; } + + // KeyedService: + void Shutdown() override; + + private: + // bookmarks::BaseBookmarkModelObserver: + void BookmarkModelChanged() override {} + void BookmarkModelLoaded(bookmarks::BookmarkModel* model, + bool ids_reassigned) override; + void BookmarkModelBeingDeleted(bookmarks::BookmarkModel* model) override; + void BookmarkNodeMoved(bookmarks::BookmarkModel* model, + const bookmarks::BookmarkNode* old_parent, + int old_index, + const bookmarks::BookmarkNode* new_parent, + int new_index) override; + void BookmarkNodeAdded(bookmarks::BookmarkModel* model, + const bookmarks::BookmarkNode* parent, + int index) override; + void OnWillRemoveBookmarks(bookmarks::BookmarkModel* model, + const bookmarks::BookmarkNode* parent, + int old_index, + const bookmarks::BookmarkNode* node) override; + void OnWillRemoveAllUserBookmarks(bookmarks::BookmarkModel* model) override; + void OnWillChangeBookmarkNode(bookmarks::BookmarkModel* model, + const bookmarks::BookmarkNode* node) override; + void OnWillReorderBookmarkNode(bookmarks::BookmarkModel* model, + const bookmarks::BookmarkNode* node) override; + void GroupedBookmarkChangesBeginning( + bookmarks::BookmarkModel* model) override; + void GroupedBookmarkChangesEnded(bookmarks::BookmarkModel* model) override; + + // BookmarkRenumberObserver: + void OnBookmarkRenumbered(int64 old_id, int64 new_id) override; + + UndoManager undo_manager_; + ScopedObserver<bookmarks::BookmarkModel, bookmarks::BookmarkModelObserver> + scoped_observer_; + + DISALLOW_COPY_AND_ASSIGN(BookmarkUndoService); +}; + +#endif // COMPONENTS_UNDO_BOOKMARK_UNDO_SERVICE_H_ diff --git a/components/undo/bookmark_undo_service_test.cc b/components/undo/bookmark_undo_service_test.cc new file mode 100644 index 0000000..e97988a --- /dev/null +++ b/components/undo/bookmark_undo_service_test.cc @@ -0,0 +1,421 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/undo/bookmark_undo_service.h" + +#include "base/strings/utf_string_conversions.h" +#include "components/bookmarks/browser/bookmark_model.h" +#include "components/bookmarks/test/bookmark_test_helpers.h" +#include "components/bookmarks/test/test_bookmark_client.h" +#include "testing/gtest/include/gtest/gtest.h" + +using base::ASCIIToUTF16; +using bookmarks::BookmarkModel; +using bookmarks::BookmarkNode; + +namespace { + +class BookmarkUndoServiceTest : public testing::Test { + public: + BookmarkUndoServiceTest(); + + void SetUp() override; + void TearDown() override; + + BookmarkModel* GetModel(); + BookmarkUndoService* GetUndoService(); + + private: + scoped_ptr<bookmarks::TestBookmarkClient> test_bookmark_client_; + scoped_ptr<bookmarks::BookmarkModel> bookmark_model_; + scoped_ptr<BookmarkUndoService> bookmark_undo_service_; + + DISALLOW_COPY_AND_ASSIGN(BookmarkUndoServiceTest); +}; + +BookmarkUndoServiceTest::BookmarkUndoServiceTest() {} + +void BookmarkUndoServiceTest::SetUp() { + DCHECK(!test_bookmark_client_); + DCHECK(!bookmark_model_); + DCHECK(!bookmark_undo_service_); + test_bookmark_client_.reset(new bookmarks::TestBookmarkClient); + bookmark_model_ = test_bookmark_client_->CreateModel(); + bookmark_undo_service_.reset(new BookmarkUndoService); + bookmark_undo_service_->Start(bookmark_model_.get()); + bookmarks::test::WaitForBookmarkModelToLoad(bookmark_model_.get()); +} + +BookmarkModel* BookmarkUndoServiceTest::GetModel() { + return bookmark_model_.get(); +} + +BookmarkUndoService* BookmarkUndoServiceTest::GetUndoService() { + return bookmark_undo_service_.get(); +} + +void BookmarkUndoServiceTest::TearDown() { + // Implement two-phase KeyedService shutdown for test KeyedServices. + bookmark_undo_service_->Shutdown(); + bookmark_model_->Shutdown(); + test_bookmark_client_->Shutdown(); + bookmark_undo_service_.reset(); + bookmark_model_.reset(); + test_bookmark_client_.reset(); +} + +TEST_F(BookmarkUndoServiceTest, AddBookmark) { + BookmarkModel* model = GetModel(); + BookmarkUndoService* undo_service = GetUndoService(); + + const BookmarkNode* parent = model->other_node(); + model->AddURL(parent, 0, ASCIIToUTF16("foo"), GURL("http://www.bar.com")); + + // Undo bookmark creation and test for no bookmarks. + undo_service->undo_manager()->Undo(); + EXPECT_EQ(0, model->other_node()->child_count()); + + // Redo bookmark creation and ensure bookmark information is valid. + undo_service->undo_manager()->Redo(); + const BookmarkNode* node = parent->GetChild(0); + EXPECT_EQ(node->GetTitle(), ASCIIToUTF16("foo")); + EXPECT_EQ(node->url(), GURL("http://www.bar.com")); +} + +// Test that a bookmark removal action can be undone and redone. +TEST_F(BookmarkUndoServiceTest, UndoBookmarkRemove) { + BookmarkModel* model = GetModel(); + BookmarkUndoService* undo_service = GetUndoService(); + + const BookmarkNode* parent = model->other_node(); + model->AddURL(parent, 0, ASCIIToUTF16("foo"), GURL("http://www.bar.com")); + model->Remove(parent, 0); + + EXPECT_EQ(2U, undo_service->undo_manager()->undo_count()); + EXPECT_EQ(0U, undo_service->undo_manager()->redo_count()); + + // Undo the deletion of the only bookmark and check the bookmark values. + undo_service->undo_manager()->Undo(); + EXPECT_EQ(1, model->other_node()->child_count()); + const BookmarkNode* node = parent->GetChild(0); + EXPECT_EQ(node->GetTitle(), ASCIIToUTF16("foo")); + EXPECT_EQ(node->url(), GURL("http://www.bar.com")); + + EXPECT_EQ(1U, undo_service->undo_manager()->undo_count()); + EXPECT_EQ(1U, undo_service->undo_manager()->redo_count()); + + // Redo the deletion and check that there are no bookmarks left. + undo_service->undo_manager()->Redo(); + EXPECT_EQ(0, model->other_node()->child_count()); + + EXPECT_EQ(2U, undo_service->undo_manager()->undo_count()); + EXPECT_EQ(0U, undo_service->undo_manager()->redo_count()); +} + +// Ensure the undo/redo works for editing of bookmark information grouped into +// one action. +TEST_F(BookmarkUndoServiceTest, UndoBookmarkGroupedAction) { + BookmarkModel* model = GetModel(); + BookmarkUndoService* undo_service = GetUndoService(); + + const BookmarkNode* n1 = model->AddURL(model->other_node(), + 0, + ASCIIToUTF16("foo"), + GURL("http://www.foo.com")); + undo_service->undo_manager()->StartGroupingActions(); + model->SetTitle(n1, ASCIIToUTF16("bar")); + model->SetURL(n1, GURL("http://www.bar.com")); + undo_service->undo_manager()->EndGroupingActions(); + + EXPECT_EQ(2U, undo_service->undo_manager()->undo_count()); + EXPECT_EQ(0U, undo_service->undo_manager()->redo_count()); + + // Undo the modification of the bookmark and check for the original values. + undo_service->undo_manager()->Undo(); + EXPECT_EQ(1, model->other_node()->child_count()); + const BookmarkNode* node = model->other_node()->GetChild(0); + EXPECT_EQ(node->GetTitle(), ASCIIToUTF16("foo")); + EXPECT_EQ(node->url(), GURL("http://www.foo.com")); + + // Redo the modifications and ensure the newer values are present. + undo_service->undo_manager()->Redo(); + EXPECT_EQ(1, model->other_node()->child_count()); + node = model->other_node()->GetChild(0); + EXPECT_EQ(node->GetTitle(), ASCIIToUTF16("bar")); + EXPECT_EQ(node->url(), GURL("http://www.bar.com")); + + EXPECT_EQ(2U, undo_service->undo_manager()->undo_count()); + EXPECT_EQ(0U, undo_service->undo_manager()->redo_count()); +} + +// Test moving bookmarks within a folder and between folders. +TEST_F(BookmarkUndoServiceTest, UndoBookmarkMoveWithinFolder) { + BookmarkModel* model = GetModel(); + BookmarkUndoService* undo_service = GetUndoService(); + + const BookmarkNode* n1 = model->AddURL(model->other_node(), + 0, + ASCIIToUTF16("foo"), + GURL("http://www.foo.com")); + const BookmarkNode* n2 = model->AddURL(model->other_node(), + 1, + ASCIIToUTF16("moo"), + GURL("http://www.moo.com")); + const BookmarkNode* n3 = model->AddURL(model->other_node(), + 2, + ASCIIToUTF16("bar"), + GURL("http://www.bar.com")); + model->Move(n1, model->other_node(), 3); + + // Undo the move and check that the nodes are in order. + undo_service->undo_manager()->Undo(); + EXPECT_EQ(model->other_node()->GetChild(0), n1); + EXPECT_EQ(model->other_node()->GetChild(1), n2); + EXPECT_EQ(model->other_node()->GetChild(2), n3); + + // Redo the move and check that the first node is in the last position. + undo_service->undo_manager()->Redo(); + EXPECT_EQ(model->other_node()->GetChild(0), n2); + EXPECT_EQ(model->other_node()->GetChild(1), n3); + EXPECT_EQ(model->other_node()->GetChild(2), n1); +} + +// Test undo of a bookmark moved to a different folder. +TEST_F(BookmarkUndoServiceTest, UndoBookmarkMoveToOtherFolder) { + BookmarkModel* model = GetModel(); + BookmarkUndoService* undo_service = GetUndoService(); + + const BookmarkNode* n1 = model->AddURL(model->other_node(), + 0, + ASCIIToUTF16("foo"), + GURL("http://www.foo.com")); + const BookmarkNode* n2 = model->AddURL(model->other_node(), + 1, + ASCIIToUTF16("moo"), + GURL("http://www.moo.com")); + const BookmarkNode* n3 = model->AddURL(model->other_node(), + 2, + ASCIIToUTF16("bar"), + GURL("http://www.bar.com")); + const BookmarkNode* f1 = + model->AddFolder(model->other_node(), 3, ASCIIToUTF16("folder")); + model->Move(n3, f1, 0); + + // Undo the move and check that the bookmark and folder are in place. + undo_service->undo_manager()->Undo(); + ASSERT_EQ(4, model->other_node()->child_count()); + EXPECT_EQ(model->other_node()->GetChild(0), n1); + EXPECT_EQ(model->other_node()->GetChild(1), n2); + EXPECT_EQ(model->other_node()->GetChild(2), n3); + EXPECT_EQ(model->other_node()->GetChild(3), f1); + EXPECT_EQ(0, f1->child_count()); + + // Redo the move back into the folder and check validity. + undo_service->undo_manager()->Redo(); + ASSERT_EQ(3, model->other_node()->child_count()); + EXPECT_EQ(model->other_node()->GetChild(0), n1); + EXPECT_EQ(model->other_node()->GetChild(1), n2); + EXPECT_EQ(model->other_node()->GetChild(2), f1); + ASSERT_EQ(1, f1->child_count()); + EXPECT_EQ(f1->GetChild(0), n3); +} + +// Tests the handling of multiple modifications that include renumbering of the +// bookmark identifiers. +TEST_F(BookmarkUndoServiceTest, UndoBookmarkRenameDelete) { + BookmarkModel* model = GetModel(); + BookmarkUndoService* undo_service = GetUndoService(); + + const BookmarkNode* f1 = model->AddFolder(model->other_node(), + 0, + ASCIIToUTF16("folder")); + model->AddURL(f1, 0, ASCIIToUTF16("foo"), GURL("http://www.foo.com")); + model->SetTitle(f1, ASCIIToUTF16("Renamed")); + model->Remove(model->other_node(), 0); + + // Undo the folder removal and ensure the folder and bookmark were restored. + undo_service->undo_manager()->Undo(); + ASSERT_EQ(1, model->other_node()->child_count()); + ASSERT_EQ(1, model->other_node()->GetChild(0)->child_count()); + const BookmarkNode* node = model->other_node()->GetChild(0); + EXPECT_EQ(node->GetTitle(), ASCIIToUTF16("Renamed")); + + node = model->other_node()->GetChild(0)->GetChild(0); + EXPECT_EQ(node->GetTitle(), ASCIIToUTF16("foo")); + EXPECT_EQ(node->url(), GURL("http://www.foo.com")); + + // Undo the title change and ensure the folder was updated even though the + // id has changed. + undo_service->undo_manager()->Undo(); + node = model->other_node()->GetChild(0); + EXPECT_EQ(node->GetTitle(), ASCIIToUTF16("folder")); + + // Undo bookmark creation and test for removal of bookmark. + undo_service->undo_manager()->Undo(); + ASSERT_EQ(0, model->other_node()->GetChild(0)->child_count()); + + // Undo folder creation and confirm the bookmark model is empty. + undo_service->undo_manager()->Undo(); + ASSERT_EQ(0, model->other_node()->child_count()); + + // Redo all the actions and ensure the folder and bookmark are restored. + undo_service->undo_manager()->Redo(); // folder creation + undo_service->undo_manager()->Redo(); // bookmark creation + undo_service->undo_manager()->Redo(); // bookmark title change + ASSERT_EQ(1, model->other_node()->child_count()); + ASSERT_EQ(1, model->other_node()->GetChild(0)->child_count()); + node = model->other_node()->GetChild(0); + EXPECT_EQ(node->GetTitle(), ASCIIToUTF16("Renamed")); + node = model->other_node()->GetChild(0)->GetChild(0); + EXPECT_EQ(node->GetTitle(), ASCIIToUTF16("foo")); + EXPECT_EQ(node->url(), GURL("http://www.foo.com")); + + undo_service->undo_manager()->Redo(); // folder deletion + EXPECT_EQ(0, model->other_node()->child_count()); +} + +// Test the undo of SortChildren and ReorderChildren. +TEST_F(BookmarkUndoServiceTest, UndoBookmarkReorder) { + BookmarkModel* model = GetModel(); + BookmarkUndoService* undo_service = GetUndoService(); + + const BookmarkNode* parent = model->other_node(); + model->AddURL(parent, 0, ASCIIToUTF16("foo"), GURL("http://www.foo.com")); + model->AddURL(parent, 1, ASCIIToUTF16("moo"), GURL("http://www.moo.com")); + model->AddURL(parent, 2, ASCIIToUTF16("bar"), GURL("http://www.bar.com")); + model->SortChildren(parent); + + // Test the undo of SortChildren. + undo_service->undo_manager()->Undo(); + const BookmarkNode* node = parent->GetChild(0); + EXPECT_EQ(node->GetTitle(), ASCIIToUTF16("foo")); + EXPECT_EQ(node->url(), GURL("http://www.foo.com")); + + node = parent->GetChild(1); + EXPECT_EQ(node->GetTitle(), ASCIIToUTF16("moo")); + EXPECT_EQ(node->url(), GURL("http://www.moo.com")); + + node = parent->GetChild(2); + EXPECT_EQ(node->GetTitle(), ASCIIToUTF16("bar")); + EXPECT_EQ(node->url(), GURL("http://www.bar.com")); + + // Test the redo of SortChildren. + undo_service->undo_manager()->Redo(); + node = parent->GetChild(0); + EXPECT_EQ(node->GetTitle(), ASCIIToUTF16("bar")); + EXPECT_EQ(node->url(), GURL("http://www.bar.com")); + + node = parent->GetChild(1); + EXPECT_EQ(node->GetTitle(), ASCIIToUTF16("foo")); + EXPECT_EQ(node->url(), GURL("http://www.foo.com")); + + node = parent->GetChild(2); + EXPECT_EQ(node->GetTitle(), ASCIIToUTF16("moo")); + EXPECT_EQ(node->url(), GURL("http://www.moo.com")); + +} + +TEST_F(BookmarkUndoServiceTest, UndoBookmarkRemoveAll) { + BookmarkModel* model = GetModel(); + BookmarkUndoService* undo_service = GetUndoService(); + + // Setup bookmarks in the Other Bookmarks and the Bookmark Bar. + const BookmarkNode* new_folder; + const BookmarkNode* parent = model->other_node(); + model->AddURL(parent, 0, ASCIIToUTF16("foo"), GURL("http://www.google.com")); + new_folder= model->AddFolder(parent, 1, ASCIIToUTF16("folder")); + model->AddURL(new_folder, 0, ASCIIToUTF16("bar"), GURL("http://www.bar.com")); + + parent = model->bookmark_bar_node(); + model->AddURL(parent, 0, ASCIIToUTF16("a"), GURL("http://www.a.com")); + new_folder = model->AddFolder(parent, 1, ASCIIToUTF16("folder")); + model->AddURL(new_folder, 0, ASCIIToUTF16("b"), GURL("http://www.b.com")); + + model->RemoveAllUserBookmarks(); + + // Test that the undo of RemoveAllUserBookmarks restores all folders and + // bookmarks. + undo_service->undo_manager()->Undo(); + + ASSERT_EQ(2, model->other_node()->child_count()); + EXPECT_EQ(1, model->other_node()->GetChild(1)->child_count()); + const BookmarkNode* node = model->other_node()->GetChild(1)->GetChild(0); + EXPECT_EQ(node->GetTitle(), ASCIIToUTF16("bar")); + EXPECT_EQ(node->url(), GURL("http://www.bar.com")); + + ASSERT_EQ(2, model->bookmark_bar_node()->child_count()); + EXPECT_EQ(1, model->bookmark_bar_node()->GetChild(1)->child_count()); + node = model->bookmark_bar_node()->GetChild(1)->GetChild(0); + EXPECT_EQ(node->GetTitle(), ASCIIToUTF16("b")); + EXPECT_EQ(node->url(), GURL("http://www.b.com")); + + // Test that the redo removes all folders and bookmarks. + undo_service->undo_manager()->Redo(); + EXPECT_EQ(0, model->other_node()->child_count()); + EXPECT_EQ(0, model->bookmark_bar_node()->child_count()); +} + +TEST_F(BookmarkUndoServiceTest, UndoRemoveFolderWithBookmarks) { + BookmarkModel* model = GetModel(); + BookmarkUndoService* undo_service = GetUndoService(); + + // Setup bookmarks in the Other Bookmarks. + const BookmarkNode* new_folder; + const BookmarkNode* parent = model->other_node(); + new_folder = model->AddFolder(parent, 0, ASCIIToUTF16("folder")); + model->AddURL(new_folder, 0, ASCIIToUTF16("bar"), GURL("http://www.bar.com")); + + model->Remove(parent, 0); + + // Test that the undo restores the bookmark and folder. + undo_service->undo_manager()->Undo(); + + ASSERT_EQ(1, model->other_node()->child_count()); + new_folder = model->other_node()->GetChild(0); + EXPECT_EQ(1, new_folder->child_count()); + const BookmarkNode* node = new_folder->GetChild(0); + EXPECT_EQ(node->GetTitle(), ASCIIToUTF16("bar")); + EXPECT_EQ(node->url(), GURL("http://www.bar.com")); + + // Test that the redo restores the bookmark and folder. + undo_service->undo_manager()->Redo(); + + ASSERT_EQ(0, model->other_node()->child_count()); + + // Test that the undo after a redo restores the bookmark and folder. + undo_service->undo_manager()->Undo(); + + ASSERT_EQ(1, model->other_node()->child_count()); + new_folder = model->other_node()->GetChild(0); + EXPECT_EQ(1, new_folder->child_count()); + node = new_folder->GetChild(0); + EXPECT_EQ(node->GetTitle(), ASCIIToUTF16("bar")); + EXPECT_EQ(node->url(), GURL("http://www.bar.com")); +} + +TEST_F(BookmarkUndoServiceTest, TestUpperLimit) { + BookmarkModel* model = GetModel(); + BookmarkUndoService* undo_service = GetUndoService(); + + // This maximum is set in undo_manager.cc + const size_t kMaxUndoGroups = 100; + + const BookmarkNode* parent = model->other_node(); + model->AddURL(parent, 0, ASCIIToUTF16("foo"), GURL("http://www.foo.com")); + for (size_t i = 1; i < kMaxUndoGroups + 1; ++i) + model->AddURL(parent, i, ASCIIToUTF16("bar"), GURL("http://www.bar.com")); + + EXPECT_EQ(kMaxUndoGroups, undo_service->undo_manager()->undo_count()); + + // Undo as many operations as possible. + while (undo_service->undo_manager()->undo_count()) + undo_service->undo_manager()->Undo(); + + EXPECT_EQ(1, parent->child_count()); + const BookmarkNode* node = model->other_node()->GetChild(0); + EXPECT_EQ(node->GetTitle(), ASCIIToUTF16("foo")); + EXPECT_EQ(node->url(), GURL("http://www.foo.com")); +} + +} // namespace diff --git a/components/undo/bookmark_undo_utils.cc b/components/undo/bookmark_undo_utils.cc new file mode 100644 index 0000000..1539431 --- /dev/null +++ b/components/undo/bookmark_undo_utils.cc @@ -0,0 +1,24 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/undo/bookmark_undo_utils.h" + +#include "components/undo/bookmark_undo_service.h" +#include "components/undo/undo_manager.h" + +// ScopedSuspendBookmarkUndo -------------------------------------------------- + +ScopedSuspendBookmarkUndo::ScopedSuspendBookmarkUndo( + BookmarkUndoService* bookmark_undo_service) + : undo_manager_(bookmark_undo_service + ? bookmark_undo_service->undo_manager() + : nullptr) { + if (undo_manager_) + undo_manager_->SuspendUndoTracking(); +} + +ScopedSuspendBookmarkUndo::~ScopedSuspendBookmarkUndo() { + if (undo_manager_) + undo_manager_->ResumeUndoTracking(); +} diff --git a/components/undo/bookmark_undo_utils.h b/components/undo/bookmark_undo_utils.h new file mode 100644 index 0000000..987e2728 --- /dev/null +++ b/components/undo/bookmark_undo_utils.h @@ -0,0 +1,29 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_UNDO_BOOKMARK_UNDO_UTILS_H_ +#define COMPONENTS_UNDO_BOOKMARK_UNDO_UTILS_H_ + +#include "base/basictypes.h" + +class BookmarkUndoService; +class UndoManager; + +// ScopedSuspendBookmarkUndo -------------------------------------------------- + +// Scopes the suspension of the undo tracking for non-user initiated changes +// such as those occuring during account synchronization. +class ScopedSuspendBookmarkUndo { + public: + explicit ScopedSuspendBookmarkUndo( + BookmarkUndoService* bookmark_undo_service); + ~ScopedSuspendBookmarkUndo(); + + private: + UndoManager* undo_manager_; + + DISALLOW_COPY_AND_ASSIGN(ScopedSuspendBookmarkUndo); +}; + +#endif // COMPONENTS_UNDO_BOOKMARK_UNDO_UTILS_H_ diff --git a/components/undo/undo_manager.cc b/components/undo/undo_manager.cc new file mode 100644 index 0000000..28a8ab2 --- /dev/null +++ b/components/undo/undo_manager.cc @@ -0,0 +1,224 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/undo/undo_manager.h" + +#include "base/auto_reset.h" +#include "base/logging.h" +#include "components/undo/undo_manager_observer.h" +#include "components/undo/undo_operation.h" +#include "grit/components_strings.h" +#include "ui/base/l10n/l10n_util.h" + +namespace { + +// Maximum number of changes that can be undone. +const size_t kMaxUndoGroups = 100; + +} // namespace + +// UndoGroup ------------------------------------------------------------------ + +UndoGroup::UndoGroup() + : undo_label_id_(IDS_BOOKMARK_BAR_UNDO), + redo_label_id_(IDS_BOOKMARK_BAR_REDO) { +} + +UndoGroup::~UndoGroup() { +} + +void UndoGroup::AddOperation(scoped_ptr<UndoOperation> operation) { + if (operations_.empty()) { + set_undo_label_id(operation->GetUndoLabelId()); + set_redo_label_id(operation->GetRedoLabelId()); + } + operations_.push_back(operation.release()); +} + +void UndoGroup::Undo() { + for (ScopedVector<UndoOperation>::reverse_iterator ri = operations_.rbegin(); + ri != operations_.rend(); ++ri) { + (*ri)->Undo(); + } +} + +// UndoManager ---------------------------------------------------------------- + +UndoManager::UndoManager() + : group_actions_count_(0), + undo_in_progress_action_(NULL), + undo_suspended_count_(0), + performing_undo_(false), + performing_redo_(false) { +} + +UndoManager::~UndoManager() { + DCHECK_EQ(0, group_actions_count_); + DCHECK_EQ(0, undo_suspended_count_); + DCHECK(!performing_undo_); + DCHECK(!performing_redo_); +} + +void UndoManager::Undo() { + Undo(&performing_undo_, &undo_actions_); +} + +void UndoManager::Redo() { + Undo(&performing_redo_, &redo_actions_); +} + +base::string16 UndoManager::GetUndoLabel() const { + return l10n_util::GetStringUTF16( + undo_actions_.empty() ? IDS_BOOKMARK_BAR_UNDO + : undo_actions_.back()->get_undo_label_id()); +} + +base::string16 UndoManager::GetRedoLabel() const { + return l10n_util::GetStringUTF16( + redo_actions_.empty() ? IDS_BOOKMARK_BAR_REDO + : redo_actions_.back()->get_redo_label_id()); +} + +void UndoManager::AddUndoOperation(scoped_ptr<UndoOperation> operation) { + if (IsUndoTrakingSuspended()) { + RemoveAllOperations(); + operation.reset(); + return; + } + + if (group_actions_count_) { + pending_grouped_action_->AddOperation(operation.Pass()); + } else { + UndoGroup* new_action = new UndoGroup(); + new_action->AddOperation(operation.Pass()); + AddUndoGroup(new_action); + } +} + +void UndoManager::StartGroupingActions() { + if (!group_actions_count_) + pending_grouped_action_.reset(new UndoGroup()); + ++group_actions_count_; +} + +void UndoManager::EndGroupingActions() { + --group_actions_count_; + if (group_actions_count_ > 0) + return; + + // Check that StartGroupingActions and EndGroupingActions are paired. + DCHECK_GE(group_actions_count_, 0); + + bool is_user_action = !performing_undo_ && !performing_redo_; + if (!pending_grouped_action_->undo_operations().empty()) { + AddUndoGroup(pending_grouped_action_.release()); + } else { + // No changes were executed since we started grouping actions, so the + // pending UndoGroup should be discarded. + pending_grouped_action_.reset(); + + // This situation is only expected when it is a user initiated action. + // Undo/Redo should have at least one operation performed. + DCHECK(is_user_action); + } +} + +void UndoManager::SuspendUndoTracking() { + ++undo_suspended_count_; +} + +void UndoManager::ResumeUndoTracking() { + DCHECK_GT(undo_suspended_count_, 0); + --undo_suspended_count_; +} + +bool UndoManager::IsUndoTrakingSuspended() const { + return undo_suspended_count_ > 0; +} + +std::vector<UndoOperation*> UndoManager::GetAllUndoOperations() const { + std::vector<UndoOperation*> result; + for (size_t i = 0; i < undo_actions_.size(); ++i) { + const std::vector<UndoOperation*>& operations = + undo_actions_[i]->undo_operations(); + result.insert(result.end(), operations.begin(), operations.end()); + } + for (size_t i = 0; i < redo_actions_.size(); ++i) { + const std::vector<UndoOperation*>& operations = + redo_actions_[i]->undo_operations(); + result.insert(result.end(), operations.begin(), operations.end()); + } + // Ensure that if an Undo is in progress the UndoOperations part of that + // UndoGroup are included in the returned set. This will ensure that any + // changes (such as renumbering) will be applied to any potentially + // unprocessed UndoOperations. + if (undo_in_progress_action_) { + const std::vector<UndoOperation*>& operations = + undo_in_progress_action_->undo_operations(); + result.insert(result.end(), operations.begin(), operations.end()); + } + + return result; +} + +void UndoManager::RemoveAllOperations() { + DCHECK(!group_actions_count_); + undo_actions_.clear(); + redo_actions_.clear(); + + NotifyOnUndoManagerStateChange(); +} + +void UndoManager::AddObserver(UndoManagerObserver* observer) { + observers_.AddObserver(observer); +} + +void UndoManager::RemoveObserver(UndoManagerObserver* observer) { + observers_.RemoveObserver(observer); +} + +void UndoManager::Undo(bool* performing_indicator, + ScopedVector<UndoGroup>* active_undo_group) { + // Check that action grouping has been correctly ended. + DCHECK(!group_actions_count_); + + if (active_undo_group->empty()) + return; + + base::AutoReset<bool> incoming_changes(performing_indicator, true); + scoped_ptr<UndoGroup> action(active_undo_group->back()); + base::AutoReset<UndoGroup*> action_context(&undo_in_progress_action_, + action.get()); + active_undo_group->weak_erase( + active_undo_group->begin() + active_undo_group->size() - 1); + + StartGroupingActions(); + action->Undo(); + EndGroupingActions(); + + NotifyOnUndoManagerStateChange(); +} + +void UndoManager::NotifyOnUndoManagerStateChange() { + FOR_EACH_OBSERVER( + UndoManagerObserver, observers_, OnUndoManagerStateChange()); +} + +void UndoManager::AddUndoGroup(UndoGroup* new_undo_group) { + GetActiveUndoGroup()->push_back(new_undo_group); + + // User actions invalidate any available redo actions. + if (is_user_action()) + redo_actions_.clear(); + + // Limit the number of undo levels so the undo stack does not grow unbounded. + if (GetActiveUndoGroup()->size() > kMaxUndoGroups) + GetActiveUndoGroup()->erase(GetActiveUndoGroup()->begin()); + + NotifyOnUndoManagerStateChange(); +} + +ScopedVector<UndoGroup>* UndoManager::GetActiveUndoGroup() { + return performing_undo_ ? &redo_actions_ : &undo_actions_; +} diff --git a/components/undo/undo_manager.h b/components/undo/undo_manager.h new file mode 100644 index 0000000..ebbe1b4 --- /dev/null +++ b/components/undo/undo_manager.h @@ -0,0 +1,134 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_UNDO_UNDO_MANAGER_H_ +#define COMPONENTS_UNDO_UNDO_MANAGER_H_ + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/scoped_vector.h" +#include "base/observer_list.h" +#include "base/strings/string16.h" + +class UndoManagerObserver; +class UndoOperation; + +// UndoGroup ------------------------------------------------------------------ + +// UndoGroup represents a user action and stores all the operations that +// make that action. Typically there is only one operation per UndoGroup. +class UndoGroup { + public: + UndoGroup(); + ~UndoGroup(); + + void AddOperation(scoped_ptr<UndoOperation> operation); + const std::vector<UndoOperation*>& undo_operations() { + return operations_.get(); + } + void Undo(); + + // The resource string id describing the undo and redo action. + int get_undo_label_id() const { return undo_label_id_; } + void set_undo_label_id(int label_id) { undo_label_id_ = label_id; } + + int get_redo_label_id() const { return redo_label_id_; } + void set_redo_label_id(int label_id) { redo_label_id_ = label_id; } + + private: + ScopedVector<UndoOperation> operations_; + + // The resource string id describing the undo and redo action. + int undo_label_id_; + int redo_label_id_; + + DISALLOW_COPY_AND_ASSIGN(UndoGroup); +}; + +// UndoManager ---------------------------------------------------------------- + +// Maintains user actions as a group of operations that store enough info to +// undo and redo those operations. +class UndoManager { + public: + UndoManager(); + ~UndoManager(); + + // Perform an undo or redo operation. + void Undo(); + void Redo(); + + size_t undo_count() const { return undo_actions_.size(); } + size_t redo_count() const { return redo_actions_.size(); } + + base::string16 GetUndoLabel() const; + base::string16 GetRedoLabel() const; + + void AddUndoOperation(scoped_ptr<UndoOperation> operation); + + // Group multiple operations into one undoable action. + void StartGroupingActions(); + void EndGroupingActions(); + + // Suspend undo tracking while processing non-user initiated changes such as + // profile synchonization. + void SuspendUndoTracking(); + void ResumeUndoTracking(); + bool IsUndoTrakingSuspended() const; + + // Returns all UndoOperations that are awaiting Undo or Redo. Note that + // ownership of the UndoOperations is retained by UndoManager. + std::vector<UndoOperation*> GetAllUndoOperations() const; + + // Remove all undo and redo operations. Note that grouping of actions and + // suspension of undo tracking states are left unchanged. + void RemoveAllOperations(); + + // Observers are notified when the internal state of this class changes. + void AddObserver(UndoManagerObserver* observer); + void RemoveObserver(UndoManagerObserver* observer); + + private: + void Undo(bool* performing_indicator, + ScopedVector<UndoGroup>* active_undo_group); + bool is_user_action() const { return !performing_undo_ && !performing_redo_; } + + // Notifies the observers that the undo manager's state has changed. + void NotifyOnUndoManagerStateChange(); + + // Handle the addition of |new_undo_group| to the active undo group container. + void AddUndoGroup(UndoGroup* new_undo_group); + + // Returns the undo or redo UndoGroup container that should store the next + // change taking into account if an undo or redo is being executed. + ScopedVector<UndoGroup>* GetActiveUndoGroup(); + + // Containers of user actions ready for an undo or redo treated as a stack. + ScopedVector<UndoGroup> undo_actions_; + ScopedVector<UndoGroup> redo_actions_; + + // The observers to notify when internal state changes. + ObserverList<UndoManagerObserver> observers_; + + // Supports grouping operations into a single undo action. + int group_actions_count_; + + // The container that is used when actions are grouped. + scoped_ptr<UndoGroup> pending_grouped_action_; + + // The action that is in the process of being undone. + UndoGroup* undo_in_progress_action_; + + // Supports the suspension of undo tracking. + int undo_suspended_count_; + + // Set when executing Undo or Redo so that incoming changes are correctly + // processed. + bool performing_undo_; + bool performing_redo_; + + DISALLOW_COPY_AND_ASSIGN(UndoManager); +}; + +#endif // COMPONENTS_UNDO_UNDO_MANAGER_H_ diff --git a/components/undo/undo_manager_observer.h b/components/undo/undo_manager_observer.h new file mode 100644 index 0000000..1eecd66 --- /dev/null +++ b/components/undo/undo_manager_observer.h @@ -0,0 +1,18 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_UNDO_UNDO_MANAGER_OBSERVER_H_ +#define COMPONENTS_UNDO_UNDO_MANAGER_OBSERVER_H_ + +// Observer for the UndoManager. +class UndoManagerObserver { + public: + // Invoked when the internal state of the UndoManager has changed. + virtual void OnUndoManagerStateChange() = 0; + + protected: + virtual ~UndoManagerObserver() {} +}; + +#endif // COMPONENTS_UNDO_UNDO_MANAGER_OBSERVER_H_ diff --git a/components/undo/undo_manager_test.cc b/components/undo/undo_manager_test.cc new file mode 100644 index 0000000..5053572 --- /dev/null +++ b/components/undo/undo_manager_test.cc @@ -0,0 +1,265 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/auto_reset.h" +#include "components/undo/undo_manager.h" +#include "components/undo/undo_manager_observer.h" +#include "components/undo/undo_operation.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +class TestUndoOperation; + +// TestUndoService ------------------------------------------------------------- + +class TestUndoService { + public: + TestUndoService(); + ~TestUndoService(); + + void Redo(); + void TriggerOperation(); + void RecordUndoCall(); + + UndoManager undo_manager_; + + bool performing_redo_; + + int undo_operation_count_; + int redo_operation_count_; +}; + +// TestUndoOperation ----------------------------------------------------------- + +class TestUndoOperation : public UndoOperation { + public: + explicit TestUndoOperation(TestUndoService* undo_service); + ~TestUndoOperation() override; + + // UndoOperation: + void Undo() override; + int GetUndoLabelId() const override; + int GetRedoLabelId() const override; + + private: + TestUndoService* undo_service_; + + DISALLOW_COPY_AND_ASSIGN(TestUndoOperation); +}; + +TestUndoOperation::TestUndoOperation(TestUndoService* undo_service) + : undo_service_(undo_service) { +} + +TestUndoOperation::~TestUndoOperation() { +} + +void TestUndoOperation::Undo() { + undo_service_->TriggerOperation(); + undo_service_->RecordUndoCall(); +} + +int TestUndoOperation::GetUndoLabelId() const { + return 0; +} + +int TestUndoOperation::GetRedoLabelId() const { + return 0; +} + +// TestUndoService ------------------------------------------------------------- + +TestUndoService::TestUndoService() : performing_redo_(false), + undo_operation_count_(0), + redo_operation_count_(0) { +} + +TestUndoService::~TestUndoService() { +} + +void TestUndoService::Redo() { + base::AutoReset<bool> incoming_changes(&performing_redo_, true); + undo_manager_.Redo(); +} + +void TestUndoService::TriggerOperation() { + undo_manager_.AddUndoOperation(make_scoped_ptr(new TestUndoOperation(this))); +} + +void TestUndoService::RecordUndoCall() { + if (performing_redo_) + ++redo_operation_count_; + else + ++undo_operation_count_; +} + +// TestObserver ---------------------------------------------------------------- + +class TestObserver : public UndoManagerObserver { + public: + TestObserver() : state_change_count_(0) {} + // Returns the number of state change callbacks + int state_change_count() { return state_change_count_; } + + void OnUndoManagerStateChange() override { ++state_change_count_; } + + private: + int state_change_count_; + + DISALLOW_COPY_AND_ASSIGN(TestObserver); +}; + +// Tests ----------------------------------------------------------------------- + +TEST(UndoServiceTest, AddUndoActions) { + TestUndoService undo_service; + + undo_service.TriggerOperation(); + undo_service.TriggerOperation(); + EXPECT_EQ(2U, undo_service.undo_manager_.undo_count()); + EXPECT_EQ(0U, undo_service.undo_manager_.redo_count()); +} + +TEST(UndoServiceTest, UndoMultipleActions) { + TestUndoService undo_service; + + undo_service.TriggerOperation(); + undo_service.TriggerOperation(); + + undo_service.undo_manager_.Undo(); + EXPECT_EQ(1U, undo_service.undo_manager_.undo_count()); + EXPECT_EQ(1U, undo_service.undo_manager_.redo_count()); + + undo_service.undo_manager_.Undo(); + EXPECT_EQ(0U, undo_service.undo_manager_.undo_count()); + EXPECT_EQ(2U, undo_service.undo_manager_.redo_count()); + + EXPECT_EQ(2, undo_service.undo_operation_count_); + EXPECT_EQ(0, undo_service.redo_operation_count_); +} + +TEST(UndoServiceTest, RedoAction) { + TestUndoService undo_service; + + undo_service.TriggerOperation(); + + undo_service.undo_manager_.Undo(); + EXPECT_EQ(0U, undo_service.undo_manager_.undo_count()); + EXPECT_EQ(1U, undo_service.undo_manager_.redo_count()); + + undo_service.Redo(); + EXPECT_EQ(1U, undo_service.undo_manager_.undo_count()); + EXPECT_EQ(0U, undo_service.undo_manager_.redo_count()); + + EXPECT_EQ(1, undo_service.undo_operation_count_); + EXPECT_EQ(1, undo_service.redo_operation_count_); +} + +TEST(UndoServiceTest, GroupActions) { + TestUndoService undo_service; + + // Add two operations in a single action. + undo_service.undo_manager_.StartGroupingActions(); + undo_service.TriggerOperation(); + undo_service.TriggerOperation(); + undo_service.undo_manager_.EndGroupingActions(); + + // Check that only one action is created. + EXPECT_EQ(1U, undo_service.undo_manager_.undo_count()); + EXPECT_EQ(0U, undo_service.undo_manager_.redo_count()); + + undo_service.undo_manager_.Undo(); + EXPECT_EQ(0U, undo_service.undo_manager_.undo_count()); + EXPECT_EQ(1U, undo_service.undo_manager_.redo_count()); + + undo_service.Redo(); + EXPECT_EQ(1U, undo_service.undo_manager_.undo_count()); + EXPECT_EQ(0U, undo_service.undo_manager_.redo_count()); + + // Check that both operations were called in Undo and Redo. + EXPECT_EQ(2, undo_service.undo_operation_count_); + EXPECT_EQ(2, undo_service.redo_operation_count_); +} + +TEST(UndoServiceTest, SuspendUndoTracking) { + TestUndoService undo_service; + + undo_service.undo_manager_.SuspendUndoTracking(); + EXPECT_TRUE(undo_service.undo_manager_.IsUndoTrakingSuspended()); + + undo_service.TriggerOperation(); + + undo_service.undo_manager_.ResumeUndoTracking(); + EXPECT_FALSE(undo_service.undo_manager_.IsUndoTrakingSuspended()); + + EXPECT_EQ(0U, undo_service.undo_manager_.undo_count()); + EXPECT_EQ(0U, undo_service.undo_manager_.redo_count()); +} + +TEST(UndoServiceTest, RedoEmptyAfterNewAction) { + TestUndoService undo_service; + + undo_service.TriggerOperation(); + undo_service.undo_manager_.Undo(); + EXPECT_EQ(0U, undo_service.undo_manager_.undo_count()); + EXPECT_EQ(1U, undo_service.undo_manager_.redo_count()); + + undo_service.TriggerOperation(); + EXPECT_EQ(1U, undo_service.undo_manager_.undo_count()); + EXPECT_EQ(0U, undo_service.undo_manager_.redo_count()); +} + +TEST(UndoServiceTest, GetAllUndoOperations) { + TestUndoService undo_service; + + undo_service.TriggerOperation(); + + undo_service.undo_manager_.StartGroupingActions(); + undo_service.TriggerOperation(); + undo_service.TriggerOperation(); + undo_service.undo_manager_.EndGroupingActions(); + + undo_service.TriggerOperation(); + + undo_service.undo_manager_.Undo(); + ASSERT_EQ(2U, undo_service.undo_manager_.undo_count()); + ASSERT_EQ(1U, undo_service.undo_manager_.redo_count()); + + std::vector<UndoOperation*> all_operations = + undo_service.undo_manager_.GetAllUndoOperations(); + EXPECT_EQ(4U, all_operations.size()); +} + +TEST(UndoServiceTest, ObserverCallbacks) { + TestObserver observer; + TestUndoService undo_service; + undo_service.undo_manager_.AddObserver(&observer); + EXPECT_EQ(0, observer.state_change_count()); + + undo_service.TriggerOperation(); + EXPECT_EQ(1, observer.state_change_count()); + + undo_service.undo_manager_.StartGroupingActions(); + undo_service.TriggerOperation(); + undo_service.TriggerOperation(); + undo_service.undo_manager_.EndGroupingActions(); + EXPECT_EQ(2, observer.state_change_count()); + + // There should be at least 1 observer callback for undo. + undo_service.undo_manager_.Undo(); + int callback_count_after_undo = observer.state_change_count(); + EXPECT_GT(callback_count_after_undo, 2); + + // There should be at least 1 observer callback for redo. + undo_service.undo_manager_.Redo(); + int callback_count_after_redo = observer.state_change_count(); + EXPECT_GT(callback_count_after_redo, callback_count_after_undo); + + undo_service.undo_manager_.RemoveObserver(&observer); + undo_service.undo_manager_.Undo(); + EXPECT_EQ(callback_count_after_redo, observer.state_change_count()); +} + +} // namespace diff --git a/components/undo/undo_operation.h b/components/undo/undo_operation.h new file mode 100644 index 0000000..b78f103 --- /dev/null +++ b/components/undo/undo_operation.h @@ -0,0 +1,26 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_UNDO_UNDO_OPERATION_H_ +#define COMPONENTS_UNDO_UNDO_OPERATION_H_ + +// Base class for all undo operations. +class UndoOperation { + public: + virtual ~UndoOperation() {} + + virtual void Undo() = 0; + + // Returns the resource string id describing the undo/redo of this operation + // for use as labels in the UI. + // Note: The labels describe the original user action, this may result in + // the meaning of the redo label being reversed. For example, an + // UndoOperation representing a deletion would have been created in order to + // redo an addition by the user. In this case, the redo label string for the + // UndoOperation of delete would be "Redo add". + virtual int GetUndoLabelId() const = 0; + virtual int GetRedoLabelId() const = 0; +}; + +#endif // COMPONENTS_UNDO_UNDO_OPERATION_H_ |