Home | History | Annotate | Download | only in undo
      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 "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