1 // 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.
5 #include "components/undo/undo_manager.h"
7 #include "base/auto_reset.h"
8 #include "base/logging.h"
9 #include "components/undo/undo_manager_observer.h"
10 #include "components/undo/undo_operation.h"
11 #include "grit/components_strings.h"
12 #include "ui/base/l10n/l10n_util.h"
16 // Maximum number of changes that can be undone.
17 const size_t kMaxUndoGroups
= 100;
21 // UndoGroup ------------------------------------------------------------------
23 UndoGroup::UndoGroup()
24 : undo_label_id_(IDS_BOOKMARK_BAR_UNDO
),
25 redo_label_id_(IDS_BOOKMARK_BAR_REDO
) {
28 UndoGroup::~UndoGroup() {
31 void UndoGroup::AddOperation(scoped_ptr
<UndoOperation
> operation
) {
32 if (operations_
.empty()) {
33 set_undo_label_id(operation
->GetUndoLabelId());
34 set_redo_label_id(operation
->GetRedoLabelId());
36 operations_
.push_back(operation
.release());
39 void UndoGroup::Undo() {
40 for (ScopedVector
<UndoOperation
>::reverse_iterator ri
= operations_
.rbegin();
41 ri
!= operations_
.rend(); ++ri
) {
46 // UndoManager ----------------------------------------------------------------
48 UndoManager::UndoManager()
49 : group_actions_count_(0),
50 undo_in_progress_action_(NULL
),
51 undo_suspended_count_(0),
52 performing_undo_(false),
53 performing_redo_(false) {
56 UndoManager::~UndoManager() {
57 DCHECK_EQ(0, group_actions_count_
);
58 DCHECK_EQ(0, undo_suspended_count_
);
59 DCHECK(!performing_undo_
);
60 DCHECK(!performing_redo_
);
63 void UndoManager::Undo() {
64 Undo(&performing_undo_
, &undo_actions_
);
67 void UndoManager::Redo() {
68 Undo(&performing_redo_
, &redo_actions_
);
71 base::string16
UndoManager::GetUndoLabel() const {
72 return l10n_util::GetStringUTF16(
73 undo_actions_
.empty() ? IDS_BOOKMARK_BAR_UNDO
74 : undo_actions_
.back()->get_undo_label_id());
77 base::string16
UndoManager::GetRedoLabel() const {
78 return l10n_util::GetStringUTF16(
79 redo_actions_
.empty() ? IDS_BOOKMARK_BAR_REDO
80 : redo_actions_
.back()->get_redo_label_id());
83 void UndoManager::AddUndoOperation(scoped_ptr
<UndoOperation
> operation
) {
84 if (IsUndoTrakingSuspended()) {
85 RemoveAllOperations();
90 if (group_actions_count_
) {
91 pending_grouped_action_
->AddOperation(operation
.Pass());
93 UndoGroup
* new_action
= new UndoGroup();
94 new_action
->AddOperation(operation
.Pass());
95 AddUndoGroup(new_action
);
99 void UndoManager::StartGroupingActions() {
100 if (!group_actions_count_
)
101 pending_grouped_action_
.reset(new UndoGroup());
102 ++group_actions_count_
;
105 void UndoManager::EndGroupingActions() {
106 --group_actions_count_
;
107 if (group_actions_count_
> 0)
110 // Check that StartGroupingActions and EndGroupingActions are paired.
111 DCHECK_GE(group_actions_count_
, 0);
113 bool is_user_action
= !performing_undo_
&& !performing_redo_
;
114 if (!pending_grouped_action_
->undo_operations().empty()) {
115 AddUndoGroup(pending_grouped_action_
.release());
117 // No changes were executed since we started grouping actions, so the
118 // pending UndoGroup should be discarded.
119 pending_grouped_action_
.reset();
121 // This situation is only expected when it is a user initiated action.
122 // Undo/Redo should have at least one operation performed.
123 DCHECK(is_user_action
);
127 void UndoManager::SuspendUndoTracking() {
128 ++undo_suspended_count_
;
131 void UndoManager::ResumeUndoTracking() {
132 DCHECK_GT(undo_suspended_count_
, 0);
133 --undo_suspended_count_
;
136 bool UndoManager::IsUndoTrakingSuspended() const {
137 return undo_suspended_count_
> 0;
140 std::vector
<UndoOperation
*> UndoManager::GetAllUndoOperations() const {
141 std::vector
<UndoOperation
*> result
;
142 for (size_t i
= 0; i
< undo_actions_
.size(); ++i
) {
143 const std::vector
<UndoOperation
*>& operations
=
144 undo_actions_
[i
]->undo_operations();
145 result
.insert(result
.end(), operations
.begin(), operations
.end());
147 for (size_t i
= 0; i
< redo_actions_
.size(); ++i
) {
148 const std::vector
<UndoOperation
*>& operations
=
149 redo_actions_
[i
]->undo_operations();
150 result
.insert(result
.end(), operations
.begin(), operations
.end());
152 // Ensure that if an Undo is in progress the UndoOperations part of that
153 // UndoGroup are included in the returned set. This will ensure that any
154 // changes (such as renumbering) will be applied to any potentially
155 // unprocessed UndoOperations.
156 if (undo_in_progress_action_
) {
157 const std::vector
<UndoOperation
*>& operations
=
158 undo_in_progress_action_
->undo_operations();
159 result
.insert(result
.end(), operations
.begin(), operations
.end());
165 void UndoManager::RemoveAllOperations() {
166 DCHECK(!group_actions_count_
);
167 undo_actions_
.clear();
168 redo_actions_
.clear();
170 NotifyOnUndoManagerStateChange();
173 void UndoManager::AddObserver(UndoManagerObserver
* observer
) {
174 observers_
.AddObserver(observer
);
177 void UndoManager::RemoveObserver(UndoManagerObserver
* observer
) {
178 observers_
.RemoveObserver(observer
);
181 void UndoManager::Undo(bool* performing_indicator
,
182 ScopedVector
<UndoGroup
>* active_undo_group
) {
183 // Check that action grouping has been correctly ended.
184 DCHECK(!group_actions_count_
);
186 if (active_undo_group
->empty())
189 base::AutoReset
<bool> incoming_changes(performing_indicator
, true);
190 scoped_ptr
<UndoGroup
> action(active_undo_group
->back());
191 base::AutoReset
<UndoGroup
*> action_context(&undo_in_progress_action_
,
193 active_undo_group
->weak_erase(
194 active_undo_group
->begin() + active_undo_group
->size() - 1);
196 StartGroupingActions();
198 EndGroupingActions();
200 NotifyOnUndoManagerStateChange();
203 void UndoManager::NotifyOnUndoManagerStateChange() {
205 UndoManagerObserver
, observers_
, OnUndoManagerStateChange());
208 void UndoManager::AddUndoGroup(UndoGroup
* new_undo_group
) {
209 GetActiveUndoGroup()->push_back(new_undo_group
);
211 // User actions invalidate any available redo actions.
212 if (is_user_action())
213 redo_actions_
.clear();
215 // Limit the number of undo levels so the undo stack does not grow unbounded.
216 if (GetActiveUndoGroup()->size() > kMaxUndoGroups
)
217 GetActiveUndoGroup()->erase(GetActiveUndoGroup()->begin());
219 NotifyOnUndoManagerStateChange();
222 ScopedVector
<UndoGroup
>* UndoManager::GetActiveUndoGroup() {
223 return performing_undo_
? &redo_actions_
: &undo_actions_
;