blob: 9acd08d1b67e1fdf18a731368844595e596d96ad [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.
#ifndef UI_ACTIONS_ACTIONS_H_
#define UI_ACTIONS_ACTIONS_H_
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "base/callback_list.h"
#include "base/component_export.h"
#include "base/containers/flat_map.h"
#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/time/time.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "ui/actions/action_id.h"
#include "ui/base/accelerators/accelerator.h"
#include "ui/base/class_property.h"
#include "ui/base/metadata/metadata_header_macros.h"
#include "ui/base/models/image_model.h"
#include "ui/events/event.h"
namespace actions {
class ActionItem;
using ActionListVector = std::vector<std::unique_ptr<ActionItem>>;
using ActionItemVector = std::vector<raw_ptr<ActionItem, VectorExperimental>>;
class COMPONENT_EXPORT(ACTIONS) ActionList {
public:
class Delegate {
public:
virtual ~Delegate() = default;
virtual void ActionListChanged() = 0;
};
explicit ActionList(Delegate* delegate);
~ActionList();
const ActionListVector& children() const { return children_; }
bool empty() const { return children_.empty(); }
ActionItem* AddAction(std::unique_ptr<ActionItem> action_item);
std::unique_ptr<ActionItem> RemoveAction(ActionItem* action_item);
// Clear the action list vector
void Reset();
private:
ActionListVector children_;
raw_ptr<Delegate> delegate_;
};
class COMPONENT_EXPORT(ACTIONS) BaseAction
: public ui::metadata::MetaDataProvider,
public ActionList::Delegate,
public ui::PropertyHandler {
public:
METADATA_HEADER_BASE(BaseAction);
BaseAction();
BaseAction(const BaseAction&) = delete;
BaseAction& operator=(const BaseAction&) = delete;
~BaseAction() override;
BaseAction* GetParent() const;
ActionItem* AddChild(std::unique_ptr<ActionItem> action_item);
std::unique_ptr<ActionItem> RemoveChild(ActionItem* action_item);
const ActionList& GetChildren() const { return children_; }
void ResetActionList();
protected:
void ActionListChanged() override;
private:
raw_ptr<BaseAction> parent_ = nullptr;
ActionList children_{this};
};
// Class returned from ActionItem::BeginUpdate() in order to allow a "batch"
// update of the ActionItem state without triggering ActionChanged callbacks
// for each state change. Will trigger one update once the instance goes out of
// scope, assuming any changes were actually made.
class COMPONENT_EXPORT(ACTIONS) ScopedActionUpdate {
public:
explicit ScopedActionUpdate(ActionItem* action_item);
ScopedActionUpdate(ScopedActionUpdate&& scoped_action_update);
ScopedActionUpdate& operator=(ScopedActionUpdate&& scoped_action_update);
~ScopedActionUpdate();
private:
raw_ptr<ActionItem> action_item_;
};
// Context object designed to allow any class property to be attached to it.
// This allows invoking the action with any additional contextual information
// without requiring the action item itself have any knowledge of that
// information.
class COMPONENT_EXPORT(ACTIONS) ActionInvocationContext
: public ui::PropertyHandler {
public:
ActionInvocationContext();
ActionInvocationContext(ActionInvocationContext&&);
ActionInvocationContext& operator=(ActionInvocationContext&&);
~ActionInvocationContext() override;
class COMPONENT_EXPORT(ACTIONS) ContextBuilder {
public:
ContextBuilder(ContextBuilder&&);
ContextBuilder& operator=(ContextBuilder&&);
~ContextBuilder();
template <typename T>
ContextBuilder&& SetProperty(const ui::ClassProperty<T>* property,
ui::metadata::ArgType<T> value) && {
context_->SetProperty(property, value);
return std::move(*this);
}
[[nodiscard]] ActionInvocationContext Build() &&;
private:
friend class ActionInvocationContext;
ContextBuilder();
std::unique_ptr<ActionInvocationContext> context_ =
std::make_unique<ActionInvocationContext>();
};
static ContextBuilder Builder();
};
class COMPONENT_EXPORT(ACTIONS) ActionItem : public BaseAction {
METADATA_HEADER(ActionItem, BaseAction)
public:
using ActionChangedCallback = ui::metadata::PropertyChangedCallback;
using InvokeActionCallback =
base::RepeatingCallback<void(ActionItem*, ActionInvocationContext)>;
class COMPONENT_EXPORT(ACTIONS) ActionItemBuilder {
public:
using ChildList = std::vector<std::unique_ptr<ActionItemBuilder>>;
ActionItemBuilder();
explicit ActionItemBuilder(InvokeActionCallback callback);
ActionItemBuilder(ActionItemBuilder&&);
ActionItemBuilder& operator=(ActionItemBuilder&&);
~ActionItemBuilder();
ActionItemBuilder& AddChild(ActionItemBuilder&& child_item) &;
ActionItemBuilder&& AddChild(ActionItemBuilder&& child_item) &&;
template <typename Child, typename... Types>
ActionItemBuilder& AddChildren(Child&& child, Types&&... args) & {
return AddChildrenImpl(&child, &args...);
}
template <typename Child, typename... Types>
ActionItemBuilder&& AddChildren(Child&& child, Types&&... args) && {
return std::move(this->AddChildrenImpl(&child, &args...));
}
template <typename ActionPtr>
ActionItemBuilder& CopyAddressTo(ActionPtr* action_address) & {
*action_address = action_item_.get();
return *this;
}
template <typename ActionPtr>
ActionItemBuilder&& CopyAddressTo(ActionPtr* action_address) && {
return std::move(this->CopyAddressTo(action_address));
}
template <typename Action>
ActionItemBuilder& CopyWeakPtrTo(base::WeakPtr<Action>* weak_ptr) & {
*weak_ptr = action_item_->GetAsWeakPtr();
return *this;
}
template <typename Action>
ActionItemBuilder&& CopyWeakPtrTo(base::WeakPtr<Action>* weak_ptr) && {
return std::move(this->CopyWeakPtrTo(weak_ptr));
}
template <typename T>
ActionItemBuilder& SetProperty(const ui::ClassProperty<T>* property,
ui::metadata::ArgType<T> value) & {
action_item_->SetProperty(property, value);
return *this;
}
template <typename T>
ActionItemBuilder&& SetProperty(const ui::ClassProperty<T>* property,
ui::metadata::ArgType<T> value) && {
return std::move(this->SetProperty(property, value));
}
ActionItemBuilder& SetAccessibleName(
const std::u16string accessible_name) &;
ActionItemBuilder&& SetAccessibleName(
const std::u16string accessible_name) &&;
ActionItemBuilder& SetActionId(absl::optional<ActionId> action_id) &;
ActionItemBuilder&& SetActionId(absl::optional<ActionId> action_id) &&;
ActionItemBuilder& SetAccelerator(ui::Accelerator accelerator) &;
ActionItemBuilder&& SetAccelerator(ui::Accelerator accelerator) &&;
ActionItemBuilder& SetChecked(bool checked) &;
ActionItemBuilder&& SetChecked(bool checked) &&;
ActionItemBuilder& SetEnabled(bool enabled) &;
ActionItemBuilder&& SetEnabled(bool enabled) &&;
ActionItemBuilder& SetGroupId(absl::optional<int> group_id) &;
ActionItemBuilder&& SetGroupId(absl::optional<int> group_id) &&;
ActionItemBuilder& SetImage(const ui::ImageModel& image) &;
ActionItemBuilder&& SetImage(const ui::ImageModel& image) &&;
ActionItemBuilder& SetText(const std::u16string& text) &;
ActionItemBuilder&& SetText(const std::u16string& text) &&;
ActionItemBuilder& SetTooltipText(const std::u16string& tooltip) &;
ActionItemBuilder&& SetTooltipText(const std::u16string& tooltip) &&;
ActionItemBuilder& SetVisible(bool visible) &;
ActionItemBuilder&& SetVisible(bool visible) &&;
ActionItemBuilder& SetInvokeActionCallback(InvokeActionCallback callback) &;
ActionItemBuilder&& SetInvokeActionCallback(
InvokeActionCallback callback) &&;
[[nodiscard]] std::unique_ptr<ActionItem> Build() &&;
private:
template <typename... Args>
ActionItemBuilder& AddChildrenImpl(Args*... args) & {
std::vector<ActionItemBuilder*> children = {args...};
for (auto* child : children) {
children_.emplace_back(child->Release());
}
return *this;
}
void CreateChildren();
[[nodiscard]] std::unique_ptr<ActionItemBuilder> Release();
std::unique_ptr<ActionItem> action_item_;
ChildList children_;
};
ActionItem();
explicit ActionItem(InvokeActionCallback callback);
ActionItem(const ActionItem&) = delete;
ActionItem& operator=(const ActionItem&) = delete;
~ActionItem() override;
// Build an action.
static ActionItemBuilder Builder(InvokeActionCallback callback);
static ActionItemBuilder Builder();
// Configure action states and attributes.
std::u16string GetAccessibleName() const;
void SetAccessibleName(const std::u16string accessible_name);
absl::optional<ActionId> GetActionId() const;
void SetActionId(absl::optional<ActionId> action_id);
ui::Accelerator GetAccelerator() const;
void SetAccelerator(ui::Accelerator accelerator);
bool GetChecked() const;
void SetChecked(bool checked);
bool GetEnabled() const;
void SetEnabled(bool enabled);
absl::optional<int> GetGroupId() const;
void SetGroupId(absl::optional<int> group_id);
const ui::ImageModel& GetImage() const;
void SetImage(const ui::ImageModel& image);
const std::u16string GetText() const;
void SetText(const std::u16string& text);
const std::u16string GetTooltipText() const;
void SetTooltipText(const std::u16string& tooltip);
bool GetVisible() const;
void SetVisible(bool visible);
void SetInvokeActionCallback(InvokeActionCallback callback);
[[nodiscard]] base::CallbackListSubscription AddActionChangedCallback(
ActionChangedCallback callback);
// Alternative terms used to identify this action. Used for search indexing.
void AddSynonyms(std::initializer_list<std::u16string> synonyms);
// Do a "batch" update of the ActionItem state without triggering
// ActionChanged callbacks for each state change.
[[nodiscard]] ScopedActionUpdate BeginUpdate();
// Invoke an action.
void InvokeAction(
ActionInvocationContext context = ActionInvocationContext());
// Get action metrics.
int GetInvokeCount() const;
absl::optional<base::TimeTicks> GetLastInvokeTime() const;
base::WeakPtr<ActionItem> GetAsWeakPtr();
protected:
// ActionList::Delegate override.
void ActionListChanged() override;
void ActionItemChanged();
private:
friend class ScopedActionUpdate;
void EndUpdate();
using Synonyms = std::vector<std::u16string>;
// When `updating_` > 0, calling ActionItemChanged() will only record whether
// is item was updated in `updated_`. Once `updating_` returns to 0 and
// `updated_` = true, the ActionChanged callbacks will trigger.
int updating_ = 0;
bool updated_ = false;
std::u16string accessible_name_;
absl::optional<ActionId> action_id_;
ui::Accelerator accelerator_;
bool checked_ = false;
bool enabled_ = true;
absl::optional<int> group_id_;
bool visible_ = true;
std::u16string text_;
std::u16string tooltip_;
ui::ImageModel image_;
Synonyms synonyms_;
InvokeActionCallback callback_;
int invoke_count_ = 0;
absl::optional<base::TimeTicks> last_invoke_time_;
base::WeakPtrFactory<ActionItem> weak_ptr_factory_{this};
};
class COMPONENT_EXPORT(ACTIONS) ActionManager
: public ui::metadata::MetaDataProvider {
public:
METADATA_HEADER_BASE(ActionManager);
using ActionItemInitializerList =
base::RepeatingCallbackList<void(ActionManager*)>;
ActionManager(const ActionManager&) = delete;
ActionManager& operator=(const ActionManager&) = delete;
static ActionManager& Get();
static ActionManager& GetForTesting();
static void ResetForTesting();
void IndexActions();
ActionItem* FindAction(std::u16string term, ActionItem* scope = nullptr);
ActionItem* FindAction(ActionId action_id, ActionItem* scope = nullptr);
ActionItem* FindAction(const ui::KeyEvent& key_event,
ActionItem* scope = nullptr);
void GetActions(ActionItemVector& items, ActionItem* scope = nullptr);
ActionItem* AddAction(std::unique_ptr<ActionItem> action_item);
std::unique_ptr<ActionItem> RemoveAction(ActionItem* action_item);
template <typename Action, typename... Types>
void AddActions(Action&& action, Types&&... args) & {
AddActionsImpl(&action, &args...);
}
// Clears the actions stored in `root_action_parent_`.
void ResetActions();
// Resets the current `initializer_list_`.
void ResetActionItemInitializerList();
// Appends `initializer` to the end of the current `initializer_list_`. If the
// initializers have already been run or actions have already been added to
// the manager, the initializer will be run immediately.
[[nodiscard]] base::CallbackListSubscription AppendActionItemInitializer(
ActionItemInitializerList::CallbackType initializer);
protected:
ActionManager();
~ActionManager() override;
private:
template <typename... Args>
void AddActionsImpl(Args*... args) {
std::vector<std::unique_ptr<ActionItem>*> actions = {args...};
for (auto* action : actions) {
AddAction(std::move(*action));
}
}
ActionItem* FindActionImpl(ActionId action_id, const ActionList& list);
void GetActionsImpl(ActionItem* item, ActionItemVector& items);
// Holds the chain of ActionManager initializer callbacks.
std::unique_ptr<ActionItemInitializerList> initializer_list_;
// All "root" actions are parented to this action.
BaseAction root_action_parent_;
};
class COMPONENT_EXPORT(ACTIONS) ActionIdMap {
public:
using ActionIdToStringMap = base::flat_map<ActionId, std::string>;
using StringToActionIdMap = base::flat_map<std::string, ActionId>;
ActionIdMap(const ActionIdMap&) = delete;
ActionIdMap& operator=(const ActionIdMap&) = delete;
// Searches existing maps for the given ActionId and returns the corresponding
// string if found, otherwise returns an empty string.
static absl::optional<std::string> ActionIdToString(const ActionId action_id);
// Searches existing maps for the given string and returns the corresponding
// ActionId if found, otherwise returns kActionsEnd.
static absl::optional<ActionId> StringToActionId(
const std::string action_id_string);
static std::vector<absl::optional<std::string>> ActionIdsToStrings(
std::vector<ActionId> action_ids);
static std::vector<absl::optional<ActionId>> StringsToActionIds(
std::vector<std::string> action_id_strings);
static void AddActionIdToStringMappings(ActionIdToStringMap map);
static void AddStringToActionIdMappings(StringToActionIdMap map);
// The second element in the pair is set to true if a new ActionId is
// created, or false if an ActionId with the given name already exists.
static std::pair<ActionId, bool> CreateActionId(
const std::string& action_name);
static void ResetMapsForTesting();
private:
// Merges `map2` into `map1`.
template <typename T, typename U>
static void MergeMaps(base::flat_map<T, U>& map1, base::flat_map<T, U>& map2);
static absl::optional<ActionIdToStringMap>& GetGlobalActionIdToStringMap();
static absl::optional<StringToActionIdMap>& GetGlobalStringToActionIdMap();
static ActionIdToStringMap& GetActionIdToStringMap();
static StringToActionIdMap& GetStringToActionIdMap();
};
COMPONENT_EXPORT(ACTIONS)
extern const ui::ClassProperty<bool>* const kActionItemPinnableKey;
} // namespace actions
#endif // UI_ACTIONS_ACTIONS_H_