blob: 6829e5215908c3ef29f6ae35b40a4a1cce9aee55 [file] [log] [blame]
[email protected]be5cf822013-07-10 16:36:211// Copyright 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
sdefresnec083d1f2015-04-17 21:12:185#include "components/undo/undo_manager.h"
[email protected]be5cf822013-07-10 16:36:216
dcheng51ace48a2015-12-26 22:45:177#include <utility>
8
[email protected]be5cf822013-07-10 16:36:219#include "base/auto_reset.h"
10#include "base/logging.h"
sdefresnec083d1f2015-04-17 21:12:1811#include "components/undo/undo_manager_observer.h"
12#include "components/undo/undo_operation.h"
sdefresne5ec59f442015-04-17 07:42:2413#include "grit/components_strings.h"
[email protected]7d63a262013-12-17 17:15:5514#include "ui/base/l10n/l10n_util.h"
[email protected]be5cf822013-07-10 16:36:2115
[email protected]f0541032013-07-31 07:27:5316namespace {
17
18// Maximum number of changes that can be undone.
19const size_t kMaxUndoGroups = 100;
20
21} // namespace
22
[email protected]be5cf822013-07-10 16:36:2123// UndoGroup ------------------------------------------------------------------
24
[email protected]7d63a262013-12-17 17:15:5525UndoGroup::UndoGroup()
26 : undo_label_id_(IDS_BOOKMARK_BAR_UNDO),
27 redo_label_id_(IDS_BOOKMARK_BAR_REDO) {
[email protected]be5cf822013-07-10 16:36:2128}
29
30UndoGroup::~UndoGroup() {
31}
32
dcheng3f767dc32016-04-25 22:54:2233void UndoGroup::AddOperation(std::unique_ptr<UndoOperation> operation) {
[email protected]7d63a262013-12-17 17:15:5534 if (operations_.empty()) {
35 set_undo_label_id(operation->GetUndoLabelId());
36 set_redo_label_id(operation->GetRedoLabelId());
37 }
[email protected]be5cf822013-07-10 16:36:2138 operations_.push_back(operation.release());
39}
40
41void UndoGroup::Undo() {
42 for (ScopedVector<UndoOperation>::reverse_iterator ri = operations_.rbegin();
43 ri != operations_.rend(); ++ri) {
44 (*ri)->Undo();
45 }
46}
47
48// UndoManager ----------------------------------------------------------------
49
50UndoManager::UndoManager()
51 : group_actions_count_(0),
[email protected]cb8f79e2013-12-18 20:08:4252 undo_in_progress_action_(NULL),
[email protected]be5cf822013-07-10 16:36:2153 undo_suspended_count_(0),
54 performing_undo_(false),
55 performing_redo_(false) {
56}
57
58UndoManager::~UndoManager() {
59 DCHECK_EQ(0, group_actions_count_);
60 DCHECK_EQ(0, undo_suspended_count_);
61 DCHECK(!performing_undo_);
62 DCHECK(!performing_redo_);
63}
64
65void UndoManager::Undo() {
66 Undo(&performing_undo_, &undo_actions_);
67}
68
69void UndoManager::Redo() {
70 Undo(&performing_redo_, &redo_actions_);
71}
72
[email protected]7d63a262013-12-17 17:15:5573base::string16 UndoManager::GetUndoLabel() const {
74 return l10n_util::GetStringUTF16(
75 undo_actions_.empty() ? IDS_BOOKMARK_BAR_UNDO
76 : undo_actions_.back()->get_undo_label_id());
77}
78
79base::string16 UndoManager::GetRedoLabel() const {
80 return l10n_util::GetStringUTF16(
81 redo_actions_.empty() ? IDS_BOOKMARK_BAR_REDO
82 : redo_actions_.back()->get_redo_label_id());
83}
84
dcheng3f767dc32016-04-25 22:54:2285void UndoManager::AddUndoOperation(std::unique_ptr<UndoOperation> operation) {
[email protected]be5cf822013-07-10 16:36:2186 if (IsUndoTrakingSuspended()) {
[email protected]f0541032013-07-31 07:27:5387 RemoveAllOperations();
[email protected]be5cf822013-07-10 16:36:2188 operation.reset();
89 return;
90 }
91
92 if (group_actions_count_) {
dcheng51ace48a2015-12-26 22:45:1793 pending_grouped_action_->AddOperation(std::move(operation));
[email protected]be5cf822013-07-10 16:36:2194 } else {
95 UndoGroup* new_action = new UndoGroup();
dcheng51ace48a2015-12-26 22:45:1796 new_action->AddOperation(std::move(operation));
[email protected]f0541032013-07-31 07:27:5397 AddUndoGroup(new_action);
[email protected]be5cf822013-07-10 16:36:2198 }
99}
100
101void UndoManager::StartGroupingActions() {
102 if (!group_actions_count_)
103 pending_grouped_action_.reset(new UndoGroup());
104 ++group_actions_count_;
105}
106
107void UndoManager::EndGroupingActions() {
108 --group_actions_count_;
109 if (group_actions_count_ > 0)
110 return;
111
112 // Check that StartGroupingActions and EndGroupingActions are paired.
113 DCHECK_GE(group_actions_count_, 0);
114
115 bool is_user_action = !performing_undo_ && !performing_redo_;
[email protected]a175b9d2013-07-31 17:44:21116 if (!pending_grouped_action_->undo_operations().empty()) {
[email protected]f0541032013-07-31 07:27:53117 AddUndoGroup(pending_grouped_action_.release());
[email protected]be5cf822013-07-10 16:36:21118 } else {
119 // No changes were executed since we started grouping actions, so the
120 // pending UndoGroup should be discarded.
121 pending_grouped_action_.reset();
122
123 // This situation is only expected when it is a user initiated action.
124 // Undo/Redo should have at least one operation performed.
125 DCHECK(is_user_action);
126 }
127}
128
129void UndoManager::SuspendUndoTracking() {
130 ++undo_suspended_count_;
131}
132
133void UndoManager::ResumeUndoTracking() {
134 DCHECK_GT(undo_suspended_count_, 0);
135 --undo_suspended_count_;
136}
137
138bool UndoManager::IsUndoTrakingSuspended() const {
139 return undo_suspended_count_ > 0;
140}
141
[email protected]a175b9d2013-07-31 17:44:21142std::vector<UndoOperation*> UndoManager::GetAllUndoOperations() const {
143 std::vector<UndoOperation*> result;
144 for (size_t i = 0; i < undo_actions_.size(); ++i) {
145 const std::vector<UndoOperation*>& operations =
146 undo_actions_[i]->undo_operations();
147 result.insert(result.end(), operations.begin(), operations.end());
148 }
149 for (size_t i = 0; i < redo_actions_.size(); ++i) {
150 const std::vector<UndoOperation*>& operations =
151 redo_actions_[i]->undo_operations();
152 result.insert(result.end(), operations.begin(), operations.end());
153 }
[email protected]cb8f79e2013-12-18 20:08:42154 // Ensure that if an Undo is in progress the UndoOperations part of that
155 // UndoGroup are included in the returned set. This will ensure that any
156 // changes (such as renumbering) will be applied to any potentially
157 // unprocessed UndoOperations.
158 if (undo_in_progress_action_) {
159 const std::vector<UndoOperation*>& operations =
160 undo_in_progress_action_->undo_operations();
161 result.insert(result.end(), operations.begin(), operations.end());
162 }
[email protected]a175b9d2013-07-31 17:44:21163
164 return result;
165}
166
[email protected]f0541032013-07-31 07:27:53167void UndoManager::RemoveAllOperations() {
168 DCHECK(!group_actions_count_);
169 undo_actions_.clear();
170 redo_actions_.clear();
[email protected]0167050e2014-04-15 21:14:22171
172 NotifyOnUndoManagerStateChange();
173}
174
175void UndoManager::AddObserver(UndoManagerObserver* observer) {
176 observers_.AddObserver(observer);
177}
178
179void UndoManager::RemoveObserver(UndoManagerObserver* observer) {
180 observers_.RemoveObserver(observer);
[email protected]f0541032013-07-31 07:27:53181}
182
[email protected]be5cf822013-07-10 16:36:21183void UndoManager::Undo(bool* performing_indicator,
184 ScopedVector<UndoGroup>* active_undo_group) {
185 // Check that action grouping has been correctly ended.
186 DCHECK(!group_actions_count_);
187
188 if (active_undo_group->empty())
189 return;
190
191 base::AutoReset<bool> incoming_changes(performing_indicator, true);
dcheng3f767dc32016-04-25 22:54:22192 std::unique_ptr<UndoGroup> action(active_undo_group->back());
[email protected]cb8f79e2013-12-18 20:08:42193 base::AutoReset<UndoGroup*> action_context(&undo_in_progress_action_,
194 action.get());
[email protected]be5cf822013-07-10 16:36:21195 active_undo_group->weak_erase(
196 active_undo_group->begin() + active_undo_group->size() - 1);
197
198 StartGroupingActions();
199 action->Undo();
200 EndGroupingActions();
[email protected]0167050e2014-04-15 21:14:22201
202 NotifyOnUndoManagerStateChange();
203}
204
205void UndoManager::NotifyOnUndoManagerStateChange() {
ericwilligersff2af332016-10-19 00:16:50206 for (auto& observer : observers_)
207 observer.OnUndoManagerStateChange();
[email protected]be5cf822013-07-10 16:36:21208}
209
[email protected]f0541032013-07-31 07:27:53210void UndoManager::AddUndoGroup(UndoGroup* new_undo_group) {
211 GetActiveUndoGroup()->push_back(new_undo_group);
[email protected]be5cf822013-07-10 16:36:21212
[email protected]f0541032013-07-31 07:27:53213 // User actions invalidate any available redo actions.
214 if (is_user_action())
215 redo_actions_.clear();
216
217 // Limit the number of undo levels so the undo stack does not grow unbounded.
218 if (GetActiveUndoGroup()->size() > kMaxUndoGroups)
219 GetActiveUndoGroup()->erase(GetActiveUndoGroup()->begin());
[email protected]0167050e2014-04-15 21:14:22220
221 NotifyOnUndoManagerStateChange();
[email protected]be5cf822013-07-10 16:36:21222}
223
224ScopedVector<UndoGroup>* UndoManager::GetActiveUndoGroup() {
225 return performing_undo_ ? &redo_actions_ : &undo_actions_;
226}