blob: e43c8a3f862c81a38e39cc3178d2a2031698792c [file] [log] [blame]
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/actions/actions.h"
#include <algorithm>
#include <limits>
#include "base/no_destructor.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "ui/base/metadata/metadata_impl_macros.h"
namespace actions {
namespace {
class GlobalActionManager : public ActionManager {
public:
GlobalActionManager() = default;
GlobalActionManager(const GlobalActionManager&) = delete;
GlobalActionManager& operator=(const GlobalActionManager&) = delete;
~GlobalActionManager() override = default;
};
absl::optional<GlobalActionManager>& GetGlobalManager() {
static base::NoDestructor<absl::optional<GlobalActionManager>> manager;
return *manager;
}
} // namespace
DEFINE_UI_CLASS_PROPERTY_KEY(bool, kActionItemPinnableKey, false)
ActionList::ActionList(Delegate* delegate) : delegate_(delegate) {}
ActionList::~ActionList() = default;
ActionItem* ActionList::AddAction(std::unique_ptr<ActionItem> action_item) {
ActionItem* result = action_item.get();
children_.push_back(std::move(action_item));
if (delegate_) {
delegate_->ActionListChanged();
}
return result;
}
std::unique_ptr<ActionItem> ActionList::RemoveAction(ActionItem* action_item) {
auto result = std::find_if(
children_.begin(), children_.end(),
[action_item](auto& item) { return item.get() == action_item; });
if (result != children_.end()) {
auto result_item = std::move(*result);
children_.erase(result);
if (delegate_) {
delegate_->ActionListChanged();
}
return result_item;
}
return nullptr;
}
void ActionList::Reset() {
children_.clear();
if (delegate_) {
delegate_->ActionListChanged();
}
}
BaseAction::BaseAction() = default;
BaseAction::~BaseAction() = default;
BaseAction* BaseAction::GetParent() const {
return parent_;
}
ActionItem* BaseAction::AddChild(std::unique_ptr<ActionItem> action_item) {
DCHECK(!action_item->GetParent());
action_item->parent_ = this;
return children_.AddAction(std::move(action_item));
}
std::unique_ptr<ActionItem> BaseAction::RemoveChild(ActionItem* action_item) {
DCHECK(action_item);
DCHECK_EQ(action_item->GetParent(), this);
action_item->parent_ = nullptr;
return children_.RemoveAction(action_item);
}
void BaseAction::ActionListChanged() {}
void BaseAction::ResetActionList() {
children_.Reset();
}
BEGIN_METADATA_BASE(BaseAction)
END_METADATA
ScopedActionUpdate::ScopedActionUpdate(ActionItem* action_item)
: action_item_(action_item) {}
ScopedActionUpdate::ScopedActionUpdate(
ScopedActionUpdate&& scoped_action_update)
: action_item_(std::move(scoped_action_update.action_item_)) {
scoped_action_update.action_item_ = nullptr;
}
ScopedActionUpdate& ScopedActionUpdate::operator=(
ScopedActionUpdate&& scoped_action_update) = default;
ScopedActionUpdate::~ScopedActionUpdate() {
if (action_item_) {
action_item_->EndUpdate();
}
}
ActionInvocationContext::ContextBuilder::ContextBuilder() = default;
ActionInvocationContext::ContextBuilder::ContextBuilder(ContextBuilder&&) =
default;
ActionInvocationContext::ContextBuilder&
ActionInvocationContext::ContextBuilder::operator=(ContextBuilder&&) = default;
ActionInvocationContext::ContextBuilder::ContextBuilder::~ContextBuilder() =
default;
ActionInvocationContext ActionInvocationContext::ContextBuilder::Build() && {
return std::move(*context_);
}
ActionInvocationContext::ActionInvocationContext() = default;
ActionInvocationContext::ActionInvocationContext(ActionInvocationContext&&) =
default;
ActionInvocationContext& ActionInvocationContext::operator=(
ActionInvocationContext&&) = default;
ActionInvocationContext::~ActionInvocationContext() = default;
ActionInvocationContext::ContextBuilder ActionInvocationContext::Builder() {
return ContextBuilder();
}
ActionItem::ActionItemBuilder::ActionItemBuilder() {
action_item_ = std::make_unique<ActionItem>();
}
ActionItem::ActionItemBuilder::ActionItemBuilder(
InvokeActionCallback callback) {
action_item_ = std::make_unique<ActionItem>(std::move(callback));
}
ActionItem::ActionItemBuilder::ActionItemBuilder(
ActionItem::ActionItemBuilder&&) = default;
ActionItem::ActionItemBuilder& ActionItem::ActionItemBuilder::operator=(
ActionItem::ActionItemBuilder&&) = default;
ActionItem::ActionItemBuilder::~ActionItemBuilder() = default;
ActionItem::ActionItemBuilder& ActionItem::ActionItemBuilder::AddChild(
ActionItemBuilder&& child_item) & {
children_.emplace_back(child_item.Release());
return *this;
}
ActionItem::ActionItemBuilder&& ActionItem::ActionItemBuilder::AddChild(
ActionItemBuilder&& child_item) && {
return std::move(this->AddChild(std::move(child_item)));
}
ActionItem::ActionItemBuilder& ActionItem::ActionItemBuilder::SetAccessibleName(
const std::u16string accessible_name) & {
action_item_->SetAccessibleName(accessible_name);
return *this;
}
ActionItem::ActionItemBuilder&&
ActionItem::ActionItemBuilder::SetAccessibleName(
const std::u16string accessible_name) && {
return std::move(this->SetAccessibleName(accessible_name));
}
ActionItem::ActionItemBuilder& ActionItem::ActionItemBuilder::SetActionId(
absl::optional<ActionId> action_id) & {
action_item_->SetActionId(action_id);
return *this;
}
ActionItem::ActionItemBuilder&& ActionItem::ActionItemBuilder::SetActionId(
absl::optional<ActionId> action_id) && {
return std::move(this->SetActionId(action_id));
}
ActionItem::ActionItemBuilder& ActionItem::ActionItemBuilder::SetAccelerator(
ui::Accelerator accelerator) & {
action_item_->SetAccelerator(accelerator);
return *this;
}
ActionItem::ActionItemBuilder&& ActionItem::ActionItemBuilder::SetAccelerator(
ui::Accelerator accelerator) && {
return std::move(this->SetAccelerator(accelerator));
}
ActionItem::ActionItemBuilder& ActionItem::ActionItemBuilder::SetChecked(
bool checked) & {
action_item_->SetChecked(checked);
return *this;
}
ActionItem::ActionItemBuilder&& ActionItem::ActionItemBuilder::SetChecked(
bool checked) && {
return std::move(this->SetChecked(checked));
}
ActionItem::ActionItemBuilder& ActionItem::ActionItemBuilder::SetEnabled(
bool enabled) & {
action_item_->SetEnabled(enabled);
return *this;
}
ActionItem::ActionItemBuilder&& ActionItem::ActionItemBuilder::SetEnabled(
bool enabled) && {
return std::move(this->SetEnabled(enabled));
}
ActionItem::ActionItemBuilder& ActionItem::ActionItemBuilder::SetGroupId(
absl::optional<int> group_id) & {
action_item_->SetGroupId(group_id);
return *this;
}
ActionItem::ActionItemBuilder&& ActionItem::ActionItemBuilder::SetGroupId(
absl::optional<int> group_id) && {
return std::move(this->SetGroupId(group_id));
}
ActionItem::ActionItemBuilder& ActionItem::ActionItemBuilder::SetImage(
const ui::ImageModel& image) & {
action_item_->SetImage(image);
return *this;
}
ActionItem::ActionItemBuilder&& ActionItem::ActionItemBuilder::SetImage(
const ui::ImageModel& image) && {
return std::move(this->SetImage(image));
}
ActionItem::ActionItemBuilder& ActionItem::ActionItemBuilder::SetText(
const std::u16string& text) & {
action_item_->SetText(text);
return *this;
}
ActionItem::ActionItemBuilder&& ActionItem::ActionItemBuilder::SetText(
const std::u16string& text) && {
return std::move(this->SetText(text));
}
ActionItem::ActionItemBuilder& ActionItem::ActionItemBuilder::SetTooltipText(
const std::u16string& tooltip) & {
action_item_->SetTooltipText(tooltip);
return *this;
}
ActionItem::ActionItemBuilder&& ActionItem::ActionItemBuilder::SetTooltipText(
const std::u16string& tooltip) && {
return std::move(this->SetTooltipText(tooltip));
}
ActionItem::ActionItemBuilder& ActionItem::ActionItemBuilder::SetVisible(
bool visible) & {
action_item_->SetVisible(visible);
return *this;
}
ActionItem::ActionItemBuilder&& ActionItem::ActionItemBuilder::SetVisible(
bool visible) && {
return std::move(this->SetVisible(visible));
}
ActionItem::ActionItemBuilder&
ActionItem::ActionItemBuilder::SetInvokeActionCallback(
InvokeActionCallback callback) & {
action_item_->SetInvokeActionCallback(std::move(callback));
return *this;
}
ActionItem::ActionItemBuilder&&
ActionItem::ActionItemBuilder::SetInvokeActionCallback(
InvokeActionCallback callback) && {
return std::move(this->SetInvokeActionCallback(std::move(callback)));
}
std::unique_ptr<ActionItem> ActionItem::ActionItemBuilder::Build() && {
CreateChildren();
return std::move(action_item_);
}
void ActionItem::ActionItemBuilder::CreateChildren() {
for (auto& child : children_) {
action_item_->AddChild(std::move(*child).Build());
}
}
std::unique_ptr<ActionItem::ActionItemBuilder>
ActionItem::ActionItemBuilder::Release() {
return std::make_unique<ActionItemBuilder>(std::move(*this));
}
ActionItem::ActionItem() = default;
ActionItem::ActionItem(InvokeActionCallback callback)
: callback_(std::move(callback)) {}
ActionItem::~ActionItem() = default;
std::u16string ActionItem::GetAccessibleName() const {
return accessible_name_;
}
void ActionItem::SetAccessibleName(const std::u16string accessible_name) {
if (accessible_name_ == accessible_name) {
return;
}
accessible_name_ = accessible_name;
ActionItemChanged();
}
absl::optional<ActionId> ActionItem::GetActionId() const {
return action_id_;
}
void ActionItem::SetActionId(absl::optional<ActionId> action_id) {
if (action_id_ == action_id) {
return;
}
action_id_ = action_id;
ActionItemChanged();
}
ui::Accelerator ActionItem::GetAccelerator() const {
return accelerator_;
}
void ActionItem::SetAccelerator(ui::Accelerator accelerator) {
if (accelerator_ == accelerator) {
return;
}
accelerator_ = accelerator;
ActionItemChanged();
}
bool ActionItem::GetChecked() const {
return checked_;
}
void ActionItem::SetChecked(bool checked) {
if (checked_ == checked) {
return;
}
checked_ = checked;
if (group_id_.has_value() && checked_ && GetParent()) {
const ActionList& peer_actions = GetParent()->GetChildren();
for (auto& child : peer_actions.children()) {
if (child.get() == this) {
continue;
}
auto child_id = child->GetGroupId();
if (child_id.has_value() && group_id_ == child_id) {
child->SetChecked(false);
}
}
}
ActionItemChanged();
}
bool ActionItem::GetEnabled() const {
return enabled_;
}
void ActionItem::SetEnabled(bool enabled) {
if (enabled_ == enabled) {
return;
}
enabled_ = enabled;
ActionItemChanged();
}
absl::optional<int> ActionItem::GetGroupId() const {
return group_id_;
}
void ActionItem::SetGroupId(absl::optional<int> group_id) {
if (group_id_ == group_id) {
return;
}
group_id_ = group_id;
ActionItemChanged();
}
const ui::ImageModel& ActionItem::GetImage() const {
return image_;
}
void ActionItem::SetImage(const ui::ImageModel& image) {
if (image_ == image) {
return;
}
image_ = image;
ActionItemChanged();
}
const std::u16string ActionItem::GetText() const {
return text_;
}
void ActionItem::SetText(const std::u16string& text) {
if (text_ == text) {
return;
}
text_ = text;
ActionItemChanged();
}
const std::u16string ActionItem::GetTooltipText() const {
return tooltip_;
}
void ActionItem::SetTooltipText(const std::u16string& tooltip) {
if (tooltip_ == tooltip) {
return;
}
tooltip_ = tooltip;
ActionItemChanged();
}
bool ActionItem::GetVisible() const {
return visible_;
}
void ActionItem::SetVisible(bool visible) {
if (visible_ == visible) {
return;
}
visible_ = visible;
ActionItemChanged();
}
void ActionItem::SetInvokeActionCallback(InvokeActionCallback callback) {
if (callback_ == callback) {
return;
}
callback_ = std::move(callback);
ActionItemChanged();
}
[[nodiscard]] base::CallbackListSubscription
ActionItem::AddActionChangedCallback(ActionChangedCallback callback) {
return AddPropertyChangedCallback(this, callback);
}
// Alternative terms used to identify this action. Used for search indexing
void ActionItem::AddSynonyms(std::initializer_list<std::u16string> synonyms) {
synonyms_.insert(synonyms_.end(), synonyms);
}
void ActionItem::InvokeAction(ActionInvocationContext context) {
if (enabled_) {
invoke_count_++;
last_invoke_time_ = base::TimeTicks::Now();
if (callback_) {
callback_.Run(this, std::move(context));
}
}
}
int ActionItem::GetInvokeCount() const {
return invoke_count_;
}
absl::optional<base::TimeTicks> ActionItem::GetLastInvokeTime() const {
return last_invoke_time_;
}
base::WeakPtr<ActionItem> ActionItem::GetAsWeakPtr() {
return weak_ptr_factory_.GetWeakPtr();
}
// static
ActionItem::ActionItemBuilder ActionItem::Builder(
InvokeActionCallback callback) {
return ActionItemBuilder(std::move(callback));
}
// static
ActionItem::ActionItemBuilder ActionItem::Builder() {
return ActionItemBuilder();
}
ScopedActionUpdate ActionItem::BeginUpdate() {
++updating_;
return ScopedActionUpdate(this);
}
void ActionItem::ActionListChanged() {
BaseAction::ActionListChanged();
ActionItemChanged();
}
void ActionItem::ActionItemChanged() {
if (updating_ > 0) {
updated_ = true;
return;
}
updated_ = false;
TriggerChangedCallback(this);
}
void ActionItem::EndUpdate() {
if (updating_ > 0) {
--updating_;
if (!updating_ && updated_) {
ActionItemChanged();
}
}
}
BEGIN_METADATA(ActionItem)
ADD_PROPERTY_METADATA(std::u16string, AccessibleName)
ADD_PROPERTY_METADATA(absl::optional<ActionId>, ActionId)
ADD_PROPERTY_METADATA(ui::Accelerator, Accelerator)
ADD_PROPERTY_METADATA(bool, Checked)
ADD_PROPERTY_METADATA(bool, Enabled)
ADD_PROPERTY_METADATA(absl::optional<int>, GroupId)
ADD_PROPERTY_METADATA(std::u16string, Text)
ADD_PROPERTY_METADATA(std::u16string, TooltipText)
ADD_PROPERTY_METADATA(bool, Visible)
ADD_READONLY_PROPERTY_METADATA(int, InvokeCount)
ADD_READONLY_PROPERTY_METADATA(absl::optional<base::TimeTicks>, LastInvokeTime)
END_METADATA
ActionManager::ActionManager() {
ResetActionItemInitializerList();
}
ActionManager::~ActionManager() = default;
// static
ActionManager& ActionManager::Get() {
absl::optional<GlobalActionManager>& manager = GetGlobalManager();
if (!manager.has_value()) {
manager.emplace();
}
return manager.value();
}
// static
ActionManager& ActionManager::GetForTesting() {
return Get();
}
// static
void ActionManager::ResetForTesting() {
GetGlobalManager().reset();
}
// static
void ActionIdMap::ResetMapsForTesting() {
GetGlobalActionIdToStringMap().reset();
GetGlobalStringToActionIdMap().reset();
}
// static
absl::optional<ActionIdMap::ActionIdToStringMap>&
ActionIdMap::GetGlobalActionIdToStringMap() {
static base::NoDestructor<absl::optional<ActionIdMap::ActionIdToStringMap>>
map;
return *map;
}
// static
absl::optional<ActionIdMap::StringToActionIdMap>&
ActionIdMap::GetGlobalStringToActionIdMap() {
static base::NoDestructor<absl::optional<ActionIdMap::StringToActionIdMap>>
map;
return *map;
}
#define MAP_ACTION_IDS_TO_STRINGS
#include "ui/actions/action_id_macros.inc"
// static
ActionIdMap::ActionIdToStringMap& ActionIdMap::GetActionIdToStringMap() {
absl::optional<ActionIdMap::ActionIdToStringMap>& map =
GetGlobalActionIdToStringMap();
if (!map.has_value()) {
map.emplace(std::vector<std::pair<ActionId, std::string>>{ACTION_IDS});
}
return map.value();
}
#include "ui/actions/action_id_macros.inc"
#undef MAP_ACTION_IDS_TO_STRINGS
#define MAP_STRING_TO_ACTION_IDS
#include "ui/actions/action_id_macros.inc"
// static
ActionIdMap::StringToActionIdMap& ActionIdMap::GetStringToActionIdMap() {
absl::optional<ActionIdMap::StringToActionIdMap>& map =
GetGlobalStringToActionIdMap();
if (!map.has_value()) {
map.emplace(std::vector<std::pair<std::string, ActionId>>{ACTION_IDS});
}
return map.value();
}
#include "ui/actions/action_id_macros.inc"
#undef MAP_STRING_TO_ACTION_IDS
// static
absl::optional<std::string> ActionIdMap::ActionIdToString(
const ActionId action_id) {
auto iter = GetActionIdToStringMap().find(action_id);
if (iter != GetActionIdToStringMap().end()) {
return iter->second;
}
return absl::nullopt;
}
// static
absl::optional<ActionId> ActionIdMap::StringToActionId(
const std::string action_id_string) {
auto iter = GetStringToActionIdMap().find(action_id_string);
if (iter != GetStringToActionIdMap().end()) {
return iter->second;
}
return absl::nullopt;
}
// static
std::vector<absl::optional<std::string>> ActionIdMap::ActionIdsToStrings(
std::vector<ActionId> action_ids) {
std::vector<absl::optional<std::string>> action_id_strings;
action_id_strings.reserve(action_ids.size());
for (ActionId action_id : action_ids) {
action_id_strings.push_back(ActionIdToString(action_id));
}
return action_id_strings;
}
// static
std::vector<absl::optional<ActionId>> ActionIdMap::StringsToActionIds(
std::vector<std::string> action_id_strings) {
std::vector<absl::optional<ActionId>> action_ids;
action_ids.reserve(action_id_strings.size());
for (std::string action_id_string : action_id_strings) {
action_ids.push_back(StringToActionId(action_id_string));
}
return action_ids;
}
template <typename T, typename U>
void ActionIdMap::MergeMaps(base::flat_map<T, U>& map1,
base::flat_map<T, U>& map2) {
auto vec1 = std::move(map1).extract();
auto vec2 = std::move(map2).extract();
std::vector<std::pair<T, U>> vec3(vec1.size() + vec2.size());
std::merge(vec1.begin(), vec1.end(), vec2.begin(), vec2.end(), vec3.begin());
map1.replace(std::move(vec3));
}
// static
void ActionIdMap::AddActionIdToStringMappings(ActionIdToStringMap map) {
MergeMaps(GetActionIdToStringMap(), map);
}
// static
void ActionIdMap::AddStringToActionIdMappings(StringToActionIdMap map) {
MergeMaps(GetStringToActionIdMap(), map);
}
// static
std::pair<ActionId, bool> ActionIdMap::CreateActionId(
const std::string& action_name) {
static ActionId new_action_id = std::numeric_limits<ActionId>::max();
auto action_id = StringToActionId(action_name);
if (action_id.has_value()) {
return {action_id.value(), false};
}
GetActionIdToStringMap()[new_action_id] = action_name;
GetStringToActionIdMap()[action_name] = new_action_id;
return {new_action_id--, true};
}
void ActionManager::IndexActions() {
if (root_action_parent_.GetChildren().children().empty() &&
!initializer_list_->empty()) {
initializer_list_->Notify(this);
}
}
ActionItem* ActionManager::FindAction(std::u16string term, ActionItem* scope) {
IndexActions();
return nullptr;
}
ActionItem* ActionManager::FindAction(ActionId action_id, ActionItem* scope) {
IndexActions();
if (scope) {
auto scope_action_id = scope->GetActionId();
if (scope_action_id.has_value() && action_id == scope_action_id.value()) {
return scope;
}
}
const ActionList& action_list =
scope ? scope->GetChildren() : root_action_parent_.GetChildren();
return FindActionImpl(action_id, action_list);
}
ActionItem* ActionManager::FindAction(const ui::KeyEvent& key_event,
ActionItem* scope) {
IndexActions();
return nullptr;
}
void ActionManager::GetActions(ActionItemVector& items, ActionItem* scope) {
IndexActions();
const ActionList& action_list =
scope ? scope->GetChildren() : root_action_parent_.GetChildren();
for (auto& child : action_list.children()) {
GetActionsImpl(child.get(), items);
}
}
ActionItem* ActionManager::AddAction(std::unique_ptr<ActionItem> action_item) {
return root_action_parent_.AddChild(std::move(action_item));
}
std::unique_ptr<ActionItem> ActionManager::RemoveAction(
ActionItem* action_item) {
return root_action_parent_.RemoveChild(action_item);
}
void ActionManager::ResetActions() {
root_action_parent_.ResetActionList();
}
void ActionManager::ResetActionItemInitializerList() {
ResetActions();
initializer_list_ = std::make_unique<ActionItemInitializerList>();
}
base::CallbackListSubscription ActionManager::AppendActionItemInitializer(
ActionItemInitializerList::CallbackType initializer) {
DCHECK(initializer_list_);
// If an initializer is added after items have already been added, just run
// the initializer immediately.
if (!root_action_parent_.GetChildren().children().empty()) {
initializer.Run(this);
}
return initializer_list_->Add(std::move(initializer));
}
ActionItem* ActionManager::FindActionImpl(ActionId action_id,
const ActionList& list) {
for (const auto& item : list.children()) {
auto id = item->GetActionId();
if (id && id == action_id) {
return item.get();
}
if (!item->GetChildren().empty()) {
ActionItem* result = FindActionImpl(action_id, item->GetChildren());
if (result) {
return result;
}
}
}
return nullptr;
}
void ActionManager::GetActionsImpl(ActionItem* item, ActionItemVector& items) {
items.push_back(item);
for (auto& child : item->GetChildren().children()) {
GetActionsImpl(child.get(), items);
}
}
BEGIN_METADATA_BASE(ActionManager)
END_METADATA
} // namespace actions