blob: bb0eb837983d2b6d2c6ae64117769394dd2eacff [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 <optional>
#include <string>
#include <string_view>
#include "base/no_destructor.h"
#include "ui/actions/action_utils.h"
#include "ui/base/class_property.h"
#include "ui/base/metadata/metadata_impl_macros.h"
DEFINE_UI_CLASS_PROPERTY_TYPE(actions::ActionPinnableState)
namespace actions {
namespace {
class GlobalActionManager : public ActionManager {
public:
GlobalActionManager() = default;
GlobalActionManager(const GlobalActionManager&) = delete;
GlobalActionManager& operator=(const GlobalActionManager&) = delete;
~GlobalActionManager() override = default;
};
std::optional<GlobalActionManager>& GetGlobalManager() {
static base::NoDestructor<std::optional<GlobalActionManager>> manager;
return *manager;
}
} // namespace
DEFINE_UI_CLASS_PROPERTY_KEY(std::underlying_type_t<ActionPinnableState>,
kActionItemPinnableKey,
std::underlying_type_t<ActionPinnableState>(
ActionPinnableState::kNotPinnable))
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::ActionItem() = default;
ActionItem::ActionItem(InvokeActionCallback callback)
: callback_(std::move(callback)) {}
ActionItem::~ActionItem() = default;
std::u16string_view ActionItem::GetAccessibleName() const {
return accessible_name_;
}
void ActionItem::SetAccessibleName(std::u16string_view accessible_name) {
if (accessible_name_ == accessible_name) {
return;
}
accessible_name_ = std::u16string(accessible_name);
ActionItemChanged();
}
std::optional<ActionId> ActionItem::GetActionId() const {
return action_id_;
}
void ActionItem::SetActionId(std::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();
}
void ActionItem::AfterPropertyChange(const void* key, int64_t old_value) {
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();
}
std::optional<int> ActionItem::GetGroupId() const {
return group_id_;
}
void ActionItem::SetGroupId(std::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();
}
std::u16string_view ActionItem::GetText() const {
return text_;
}
void ActionItem::SetText(std::u16string_view text) {
if (text_ == text) {
return;
}
text_ = std::u16string(text);
ActionItemChanged();
}
std::u16string_view ActionItem::GetTooltipText() const {
return tooltip_;
}
void ActionItem::SetTooltipText(std::u16string_view tooltip) {
if (tooltip_ == tooltip) {
return;
}
tooltip_ = std::u16string(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();
}
bool ActionItem::GetIsShowingBubble() const {
return is_showing_bubble_;
}
void ActionItem::SetIsShowingBubble(bool showing_bubble) {
is_showing_bubble_ = showing_bubble;
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_;
}
std::optional<base::TimeTicks> ActionItem::GetLastInvokeTime() const {
return last_invoke_time_;
}
base::WeakPtr<ActionItem> ActionItem::GetAsWeakPtr() {
return weak_ptr_factory_.GetWeakPtr();
}
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_view, AccessibleName)
ADD_PROPERTY_METADATA(std::optional<ActionId>, ActionId)
ADD_PROPERTY_METADATA(ui::Accelerator, Accelerator)
ADD_PROPERTY_METADATA(bool, Checked)
ADD_PROPERTY_METADATA(bool, Enabled)
ADD_PROPERTY_METADATA(std::optional<int>, GroupId)
ADD_PROPERTY_METADATA(std::u16string_view, Text)
ADD_PROPERTY_METADATA(std::u16string_view, TooltipText)
ADD_PROPERTY_METADATA(bool, Visible)
ADD_READONLY_PROPERTY_METADATA(int, InvokeCount)
ADD_READONLY_PROPERTY_METADATA(std::optional<base::TimeTicks>, LastInvokeTime)
END_METADATA
StatefulImageActionItem::~StatefulImageActionItem() = default;
const ui::ImageModel& StatefulImageActionItem::GetStatefulImage() const {
return stateful_image_;
}
void StatefulImageActionItem::SetStatefulImage(
const ui::ImageModel& stateful_image) {
if (stateful_image_ == stateful_image) {
return;
}
stateful_image_ = stateful_image;
ActionItemChanged();
}
BEGIN_METADATA(StatefulImageActionItem)
ADD_PROPERTY_METADATA(ui::ImageModel, StatefulImage)
END_METADATA
ActionManager::ActionManager() {
ResetActionItemInitializerList();
}
ActionManager::~ActionManager() = default;
// static
ActionManager& ActionManager::Get() {
std::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
std::optional<ActionIdMap::ActionIdToStringMap>&
ActionIdMap::GetGlobalActionIdToStringMap() {
static base::NoDestructor<std::optional<ActionIdMap::ActionIdToStringMap>>
map;
return *map;
}
// static
std::optional<ActionIdMap::StringToActionIdMap>&
ActionIdMap::GetGlobalStringToActionIdMap() {
static base::NoDestructor<std::optional<ActionIdMap::StringToActionIdMap>>
map;
return *map;
}
#define MAP_ACTION_IDS_TO_STRINGS
#include "ui/actions/action_id_macros.inc"
// static
ActionIdMap::ActionIdToStringMap& ActionIdMap::GetActionIdToStringMap() {
std::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() {
std::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
std::optional<std::string> ActionIdMap::ActionIdToString(
const ActionId action_id) {
auto iter = GetActionIdToStringMap().find(action_id);
if (iter != GetActionIdToStringMap().end()) {
return iter->second;
}
return std::nullopt;
}
// static
std::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 std::nullopt;
}
// static
std::vector<std::optional<std::string>> ActionIdMap::ActionIdsToStrings(
std::vector<ActionId> action_ids) {
std::vector<std::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<std::optional<ActionId>> ActionIdMap::StringsToActionIds(
std::vector<std::string> action_id_strings) {
std::vector<std::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