普通文本  |  173行  |  4.69 KB

// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/undo/undo_manager.h"

#include "base/auto_reset.h"
#include "base/logging.h"
#include "chrome/browser/undo/undo_operation.h"

namespace {

// Maximum number of changes that can be undone.
const size_t kMaxUndoGroups = 100;

}  // namespace

// UndoGroup ------------------------------------------------------------------

UndoGroup::UndoGroup() {
}

UndoGroup::~UndoGroup() {
}

void UndoGroup::AddOperation(scoped_ptr<UndoOperation> operation) {
  operations_.push_back(operation.release());
}

void UndoGroup::Undo() {
  for (ScopedVector<UndoOperation>::reverse_iterator ri = operations_.rbegin();
       ri != operations_.rend(); ++ri) {
    (*ri)->Undo();
  }
}

// UndoManager ----------------------------------------------------------------

UndoManager::UndoManager()
    : group_actions_count_(0),
      undo_suspended_count_(0),
      performing_undo_(false),
      performing_redo_(false) {
}

UndoManager::~UndoManager() {
  DCHECK_EQ(0, group_actions_count_);
  DCHECK_EQ(0, undo_suspended_count_);
  DCHECK(!performing_undo_);
  DCHECK(!performing_redo_);
}

void UndoManager::Undo() {
  Undo(&performing_undo_, &undo_actions_);
}

void UndoManager::Redo() {
  Undo(&performing_redo_, &redo_actions_);
}

void UndoManager::AddUndoOperation(scoped_ptr<UndoOperation> operation) {
  if (IsUndoTrakingSuspended()) {
    RemoveAllOperations();
    operation.reset();
    return;
  }

  if (group_actions_count_) {
    pending_grouped_action_->AddOperation(operation.Pass());
  } else {
    UndoGroup* new_action = new UndoGroup();
    new_action->AddOperation(operation.Pass());
    AddUndoGroup(new_action);
  }
}

void UndoManager::StartGroupingActions() {
  if (!group_actions_count_)
    pending_grouped_action_.reset(new UndoGroup());
  ++group_actions_count_;
}

void UndoManager::EndGroupingActions() {
  --group_actions_count_;
  if (group_actions_count_ > 0)
    return;

  // Check that StartGroupingActions and EndGroupingActions are paired.
  DCHECK_GE(group_actions_count_, 0);

  bool is_user_action = !performing_undo_ && !performing_redo_;
  if (!pending_grouped_action_->undo_operations().empty()) {
    AddUndoGroup(pending_grouped_action_.release());
  } else {
    // No changes were executed since we started grouping actions, so the
    // pending UndoGroup should be discarded.
    pending_grouped_action_.reset();

    // This situation is only expected when it is a user initiated action.
    // Undo/Redo should have at least one operation performed.
    DCHECK(is_user_action);
  }
}

void UndoManager::SuspendUndoTracking() {
  ++undo_suspended_count_;
}

void UndoManager::ResumeUndoTracking() {
  DCHECK_GT(undo_suspended_count_, 0);
  --undo_suspended_count_;
}

bool UndoManager::IsUndoTrakingSuspended() const {
  return undo_suspended_count_ > 0;
}

std::vector<UndoOperation*> UndoManager::GetAllUndoOperations() const {
  std::vector<UndoOperation*> result;
  for (size_t i = 0; i < undo_actions_.size(); ++i) {
    const std::vector<UndoOperation*>& operations =
        undo_actions_[i]->undo_operations();
    result.insert(result.end(), operations.begin(), operations.end());
  }
  for (size_t i = 0; i < redo_actions_.size(); ++i) {
    const std::vector<UndoOperation*>& operations =
        redo_actions_[i]->undo_operations();
    result.insert(result.end(), operations.begin(), operations.end());
  }

  return result;
}

void UndoManager::RemoveAllOperations() {
  DCHECK(!group_actions_count_);
  undo_actions_.clear();
  redo_actions_.clear();
}

void UndoManager::Undo(bool* performing_indicator,
                       ScopedVector<UndoGroup>* active_undo_group) {
  // Check that action grouping has been correctly ended.
  DCHECK(!group_actions_count_);

  if (active_undo_group->empty())
    return;

  base::AutoReset<bool> incoming_changes(performing_indicator, true);
  scoped_ptr<UndoGroup> action(active_undo_group->back());
  active_undo_group->weak_erase(
      active_undo_group->begin() + active_undo_group->size() - 1);

  StartGroupingActions();
  action->Undo();
  EndGroupingActions();
}

void UndoManager::AddUndoGroup(UndoGroup* new_undo_group) {
  GetActiveUndoGroup()->push_back(new_undo_group);

  // User actions invalidate any available redo actions.
  if (is_user_action())
    redo_actions_.clear();

  // Limit the number of undo levels so the undo stack does not grow unbounded.
  if (GetActiveUndoGroup()->size() > kMaxUndoGroups)
    GetActiveUndoGroup()->erase(GetActiveUndoGroup()->begin());
}

ScopedVector<UndoGroup>* UndoManager::GetActiveUndoGroup() {
  return performing_undo_ ? &redo_actions_ : &undo_actions_;
}