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 14 namespace { 15 16 // Maximum number of changes that can be undone. 17 const size_t kMaxUndoGroups = 100; 18 19 } // namespace 20 21 // UndoGroup ------------------------------------------------------------------ 22 23 UndoGroup::UndoGroup() 24 : undo_label_id_(IDS_BOOKMARK_BAR_UNDO), 25 redo_label_id_(IDS_BOOKMARK_BAR_REDO) { 26 } 27 28 UndoGroup::~UndoGroup() { 29 } 30 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()); 35 } 36 operations_.push_back(operation.release()); 37 } 38 39 void 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 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) { 54 } 55 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_); 61 } 62 63 void UndoManager::Undo() { 64 Undo(&performing_undo_, &undo_actions_); 65 } 66 67 void UndoManager::Redo() { 68 Undo(&performing_redo_, &redo_actions_); 69 } 70 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()); 75 } 76 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()); 81 } 82 83 void 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 99 void UndoManager::StartGroupingActions() { 100 if (!group_actions_count_) 101 pending_grouped_action_.reset(new UndoGroup()); 102 ++group_actions_count_; 103 } 104 105 void 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 127 void UndoManager::SuspendUndoTracking() { 128 ++undo_suspended_count_; 129 } 130 131 void UndoManager::ResumeUndoTracking() { 132 DCHECK_GT(undo_suspended_count_, 0); 133 --undo_suspended_count_; 134 } 135 136 bool UndoManager::IsUndoTrakingSuspended() const { 137 return undo_suspended_count_ > 0; 138 } 139 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()); 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 165 void UndoManager::RemoveAllOperations() { 166 DCHECK(!group_actions_count_); 167 undo_actions_.clear(); 168 redo_actions_.clear(); 169 170 NotifyOnUndoManagerStateChange(); 171 } 172 173 void UndoManager::AddObserver(UndoManagerObserver* observer) { 174 observers_.AddObserver(observer); 175 } 176 177 void UndoManager::RemoveObserver(UndoManagerObserver* observer) { 178 observers_.RemoveObserver(observer); 179 } 180 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_); 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 203 void UndoManager::NotifyOnUndoManagerStateChange() { 204 FOR_EACH_OBSERVER( 205 UndoManagerObserver, observers_, OnUndoManagerStateChange()); 206 } 207 208 void 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 222 ScopedVector<UndoGroup>* UndoManager::GetActiveUndoGroup() { 223 return performing_undo_ ? &redo_actions_ : &undo_actions_; 224 } 225