blob: 47e41bed3966326cd2b49a52acf82a84a6cd6c4e [file] [log] [blame]
// 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 "components/undo/undo_manager.h"
#include <utility>
#include "base/auto_reset.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "components/undo/undo_manager_observer.h"
#include "components/undo/undo_operation.h"
#include "grit/components_strings.h"
#include "ui/base/l10n/l10n_util.h"
namespace {
// Maximum number of changes that can be undone.
const size_t kMaxUndoGroups = 100;
} // namespace
// UndoGroup ------------------------------------------------------------------
UndoGroup::UndoGroup()
: undo_label_id_(IDS_BOOKMARK_BAR_UNDO),
redo_label_id_(IDS_BOOKMARK_BAR_REDO) {
}
UndoGroup::~UndoGroup() {
}
void UndoGroup::AddOperation(std::unique_ptr<UndoOperation> operation) {
if (operations_.empty()) {
set_undo_label_id(operation->GetUndoLabelId());
set_redo_label_id(operation->GetRedoLabelId());
}
operations_.push_back(std::move(operation));
}
void UndoGroup::Undo() {
for (auto ri = operations_.rbegin(); ri != operations_.rend(); ++ri)
(*ri)->Undo();
}
// UndoManager ----------------------------------------------------------------
UndoManager::UndoManager()
: group_actions_count_(0),
undo_in_progress_action_(NULL),
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_);
}
base::string16 UndoManager::GetUndoLabel() const {
return l10n_util::GetStringUTF16(
undo_actions_.empty() ? IDS_BOOKMARK_BAR_UNDO
: undo_actions_.back()->get_undo_label_id());
}
base::string16 UndoManager::GetRedoLabel() const {
return l10n_util::GetStringUTF16(
redo_actions_.empty() ? IDS_BOOKMARK_BAR_REDO
: redo_actions_.back()->get_redo_label_id());
}
void UndoManager::AddUndoOperation(std::unique_ptr<UndoOperation> operation) {
if (IsUndoTrakingSuspended()) {
RemoveAllOperations();
operation.reset();
return;
}
if (group_actions_count_) {
pending_grouped_action_->AddOperation(std::move(operation));
} else {
UndoGroup* new_action = new UndoGroup();
new_action->AddOperation(std::move(operation));
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;
}
void UndoManager::RemoveAllOperations() {
DCHECK(!group_actions_count_);
undo_actions_.clear();
redo_actions_.clear();
NotifyOnUndoManagerStateChange();
}
void UndoManager::AddObserver(UndoManagerObserver* observer) {
observers_.AddObserver(observer);
}
void UndoManager::RemoveObserver(UndoManagerObserver* observer) {
observers_.RemoveObserver(observer);
}
void UndoManager::Undo(
bool* performing_indicator,
std::vector<std::unique_ptr<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);
std::unique_ptr<UndoGroup> action = std::move(active_undo_group->back());
base::AutoReset<UndoGroup*> action_context(&undo_in_progress_action_,
action.get());
active_undo_group->erase(active_undo_group->begin() +
active_undo_group->size() - 1);
StartGroupingActions();
action->Undo();
EndGroupingActions();
NotifyOnUndoManagerStateChange();
}
void UndoManager::NotifyOnUndoManagerStateChange() {
for (auto& observer : observers_)
observer.OnUndoManagerStateChange();
}
void UndoManager::AddUndoGroup(UndoGroup* new_undo_group) {
GetActiveUndoGroup()->push_back(base::WrapUnique<UndoGroup>(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());
NotifyOnUndoManagerStateChange();
}
std::vector<std::unique_ptr<UndoGroup>>* UndoManager::GetActiveUndoGroup() {
return performing_undo_ ? &redo_actions_ : &undo_actions_;
}