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.
4
5#include "chrome/browser/undo/undo_manager.h"
6
7#include "base/auto_reset.h"
8#include "base/logging.h"
9#include "chrome/browser/undo/undo_manager_observer.h"
10#include "chrome/browser/undo/undo_operation.h"
11#include "chrome/grit/generated_resources.h"
12#include "ui/base/l10n/l10n_util.h"
13
14namespace {
15
16// Maximum number of changes that can be undone.
17const size_t kMaxUndoGroups = 100;
18
19}  // namespace
20
21// UndoGroup ------------------------------------------------------------------
22
23UndoGroup::UndoGroup()
24    : undo_label_id_(IDS_BOOKMARK_BAR_UNDO),
25      redo_label_id_(IDS_BOOKMARK_BAR_REDO) {
26}
27
28UndoGroup::~UndoGroup() {
29}
30
31void UndoGroup::AddOperation(scoped_ptr<UndoOperation> operation) {
32  if (operations_.empty()) {
33    set_undo_label_id(operation->GetUndoLabelId());
34    set_redo_label_id(operation->GetRedoLabelId());
35  }
36  operations_.push_back(operation.release());
37}
38
39void UndoGroup::Undo() {
40  for (ScopedVector<UndoOperation>::reverse_iterator ri = operations_.rbegin();
41       ri != operations_.rend(); ++ri) {
42    (*ri)->Undo();
43  }
44}
45
46// UndoManager ----------------------------------------------------------------
47
48UndoManager::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) {
54}
55
56UndoManager::~UndoManager() {
57  DCHECK_EQ(0, group_actions_count_);
58  DCHECK_EQ(0, undo_suspended_count_);
59  DCHECK(!performing_undo_);
60  DCHECK(!performing_redo_);
61}
62
63void UndoManager::Undo() {
64  Undo(&performing_undo_, &undo_actions_);
65}
66
67void UndoManager::Redo() {
68  Undo(&performing_redo_, &redo_actions_);
69}
70
71base::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());
75}
76
77base::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());
81}
82
83void UndoManager::AddUndoOperation(scoped_ptr<UndoOperation> operation) {
84  if (IsUndoTrakingSuspended()) {
85    RemoveAllOperations();
86    operation.reset();
87    return;
88  }
89
90  if (group_actions_count_) {
91    pending_grouped_action_->AddOperation(operation.Pass());
92  } else {
93    UndoGroup* new_action = new UndoGroup();
94    new_action->AddOperation(operation.Pass());
95    AddUndoGroup(new_action);
96  }
97}
98
99void UndoManager::StartGroupingActions() {
100  if (!group_actions_count_)
101    pending_grouped_action_.reset(new UndoGroup());
102  ++group_actions_count_;
103}
104
105void UndoManager::EndGroupingActions() {
106  --group_actions_count_;
107  if (group_actions_count_ > 0)
108    return;
109
110  // Check that StartGroupingActions and EndGroupingActions are paired.
111  DCHECK_GE(group_actions_count_, 0);
112
113  bool is_user_action = !performing_undo_ && !performing_redo_;
114  if (!pending_grouped_action_->undo_operations().empty()) {
115    AddUndoGroup(pending_grouped_action_.release());
116  } else {
117    // No changes were executed since we started grouping actions, so the
118    // pending UndoGroup should be discarded.
119    pending_grouped_action_.reset();
120
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);
124  }
125}
126
127void UndoManager::SuspendUndoTracking() {
128  ++undo_suspended_count_;
129}
130
131void UndoManager::ResumeUndoTracking() {
132  DCHECK_GT(undo_suspended_count_, 0);
133  --undo_suspended_count_;
134}
135
136bool UndoManager::IsUndoTrakingSuspended() const {
137  return undo_suspended_count_ > 0;
138}
139
140std::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());
146  }
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());
151  }
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());
160  }
161
162  return result;
163}
164
165void UndoManager::RemoveAllOperations() {
166  DCHECK(!group_actions_count_);
167  undo_actions_.clear();
168  redo_actions_.clear();
169
170  NotifyOnUndoManagerStateChange();
171}
172
173void UndoManager::AddObserver(UndoManagerObserver* observer) {
174  observers_.AddObserver(observer);
175}
176
177void UndoManager::RemoveObserver(UndoManagerObserver* observer) {
178  observers_.RemoveObserver(observer);
179}
180
181void 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_);
185
186  if (active_undo_group->empty())
187    return;
188
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_,
192      action.get());
193  active_undo_group->weak_erase(
194      active_undo_group->begin() + active_undo_group->size() - 1);
195
196  StartGroupingActions();
197  action->Undo();
198  EndGroupingActions();
199
200  NotifyOnUndoManagerStateChange();
201}
202
203void UndoManager::NotifyOnUndoManagerStateChange() {
204  FOR_EACH_OBSERVER(
205      UndoManagerObserver, observers_, OnUndoManagerStateChange());
206}
207
208void UndoManager::AddUndoGroup(UndoGroup* new_undo_group) {
209  GetActiveUndoGroup()->push_back(new_undo_group);
210
211  // User actions invalidate any available redo actions.
212  if (is_user_action())
213    redo_actions_.clear();
214
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());
218
219  NotifyOnUndoManagerStateChange();
220}
221
222ScopedVector<UndoGroup>* UndoManager::GetActiveUndoGroup() {
223  return performing_undo_ ? &redo_actions_ : &undo_actions_;
224}
225