910e62b5创建于 1月15日历史提交
// 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 <optional>
#include <string>
#include <string_view>
#include <utility>
#include <vector>

#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 "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 base {
class CallbackListSubscription;
}

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();
};

template <typename BuilderT, typename ActionItemClass>
class BaseActionItemBuilderT {
 public:
  using ChildList = std::vector<std::unique_ptr<BuilderT>>;
  using ActionChangedCallback = ui::metadata::PropertyChangedCallback;
  using InvokeActionCallback =
      base::RepeatingCallback<void(ActionItem*, ActionInvocationContext)>;
  BaseActionItemBuilderT() {
    action_item_ = std::make_unique<ActionItemClass>();
  }
  explicit BaseActionItemBuilderT(InvokeActionCallback callback) {
    action_item_ = std::make_unique<ActionItemClass>(std::move(callback));
  }
  BaseActionItemBuilderT(BaseActionItemBuilderT&&) = default;
  BaseActionItemBuilderT& operator=(BaseActionItemBuilderT&&) = default;
  ~BaseActionItemBuilderT() = default;

  // Build an action.
  static BuilderT Builder(InvokeActionCallback callback) {
    return BuilderT(std::move(callback));
  }
  static BuilderT Builder() { return BuilderT(); }

  BuilderT& AddChild(BuilderT&& child_item) & {
    children_.emplace_back(child_item.Release());
    return static_cast<BuilderT&>(*this);
  }

  BuilderT&& AddChild(BuilderT&& child_item) && {
    return std::move(this->AddChild(std::move(child_item)));
  }

  template <typename Child, typename... Types>
  BuilderT& AddChildren(Child&& child, Types&&... args) & {
    return AddChildrenImpl(&child, &args...);
  }

  template <typename Child, typename... Types>
  BuilderT&& AddChildren(Child&& child, Types&&... args) && {
    return std::move(this->AddChildrenImpl(&child, &args...));
  }

  template <typename ActionPtr>
  BuilderT& CopyAddressTo(ActionPtr* action_address) & {
    *action_address = action_item_.get();
    return static_cast<BuilderT&>(*this);
  }

  template <typename ActionPtr>
  BuilderT&& CopyAddressTo(ActionPtr* action_address) && {
    return std::move(this->CopyAddressTo(action_address));
  }

  template <typename Action>
  BuilderT& CopyWeakPtrTo(base::WeakPtr<Action>* weak_ptr) & {
    *weak_ptr = action_item_->GetAsWeakPtr();
    return static_cast<BuilderT&>(*this);
  }

  template <typename Action>
  BuilderT&& CopyWeakPtrTo(base::WeakPtr<Action>* weak_ptr) && {
    return std::move(this->CopyWeakPtrTo(weak_ptr));
  }

  template <typename T>
  BuilderT& SetProperty(const ui::ClassProperty<T>* property,
                        ui::metadata::ArgType<T> value) & {
    action_item_->SetProperty(property, value);
    return static_cast<BuilderT&>(*this);
  }

  template <typename T>
  BuilderT&& SetProperty(const ui::ClassProperty<T>* property,
                         ui::metadata::ArgType<T> value) && {
    return std::move(this->SetProperty(property, value));
  }

  BuilderT& SetAccessibleName(const std::u16string accessible_name) & {
    action_item_->SetAccessibleName(accessible_name);
    return static_cast<BuilderT&>(*this);
  }

  BuilderT&& SetAccessibleName(const std::u16string accessible_name) && {
    return std::move(this->SetAccessibleName(accessible_name));
  }

  BuilderT& SetActionId(std::optional<ActionId> action_id) & {
    action_item_->SetActionId(action_id);
    return static_cast<BuilderT&>(*this);
  }

  BuilderT&& SetActionId(std::optional<ActionId> action_id) && {
    return std::move(this->SetActionId(action_id));
  }

  BuilderT& SetAccelerator(ui::Accelerator accelerator) & {
    action_item_->SetAccelerator(accelerator);
    return static_cast<BuilderT&>(*this);
  }

  BuilderT&& SetAccelerator(ui::Accelerator accelerator) && {
    return std::move(this->SetAccelerator(accelerator));
  }

  BuilderT& SetChecked(bool checked) & {
    action_item_->SetChecked(checked);
    return static_cast<BuilderT&>(*this);
  }

  BuilderT&& SetChecked(bool checked) && {
    return std::move(this->SetChecked(checked));
  }

  BuilderT& SetEnabled(bool enabled) & {
    action_item_->SetEnabled(enabled);
    return static_cast<BuilderT&>(*this);
  }

  BuilderT&& SetEnabled(bool enabled) && {
    return std::move(this->SetEnabled(enabled));
  }

  BuilderT& SetGroupId(std::optional<int> group_id) & {
    action_item_->SetGroupId(group_id);
    return static_cast<BuilderT&>(*this);
  }

  BuilderT&& SetGroupId(std::optional<int> group_id) && {
    return std::move(this->SetGroupId(group_id));
  }

  BuilderT& SetImage(const ui::ImageModel& image) & {
    action_item_->SetImage(image);
    return static_cast<BuilderT&>(*this);
  }

  BuilderT&& SetImage(const ui::ImageModel& image) && {
    return std::move(this->SetImage(image));
  }

  BuilderT& SetText(std::u16string_view text) & {
    action_item_->SetText(text);
    return static_cast<BuilderT&>(*this);
  }

  BuilderT&& SetText(std::u16string_view text) && {
    return std::move(this->SetText(text));
  }

  BuilderT& SetTooltipText(std::u16string_view tooltip) & {
    action_item_->SetTooltipText(tooltip);
    return static_cast<BuilderT&>(*this);
  }

  BuilderT&& SetTooltipText(std::u16string_view tooltip) && {
    return std::move(this->SetTooltipText(tooltip));
  }

  BuilderT& SetVisible(bool visible) & {
    action_item_->SetVisible(visible);
    return static_cast<BuilderT&>(*this);
  }

  BuilderT&& SetVisible(bool visible) && {
    return std::move(this->SetVisible(visible));
  }

  BuilderT& SetInvokeActionCallback(InvokeActionCallback callback) & {
    action_item_->SetInvokeActionCallback(std::move(callback));
    return static_cast<BuilderT&>(*this);
  }

  BuilderT&& SetInvokeActionCallback(InvokeActionCallback callback) && {
    return std::move(this->SetInvokeActionCallback(std::move(callback)));
  }

  BuilderT& SetIsShowingBubble(bool showing_bubble) & {
    action_item_->SetIsShowingBubble(showing_bubble);
    return static_cast<BuilderT&>(*this);
  }

  BuilderT&& SetIsShowingBubble(bool showing_bubble) && {
    return std::move(this->SetIsShowingBubble(showing_bubble));
  }

  [[nodiscard]] std::unique_ptr<ActionItemClass> Build() && {
    CreateChildren();
    return std::move(action_item_);
  }

 protected:
  template <typename... Args>
  BuilderT& AddChildrenImpl(Args*... args) & {
    std::vector<BuilderT*> children = {args...};
    for (auto* child : children) {
      children_.emplace_back(child->Release());
    }
    return static_cast<BuilderT&>(*this);
  }
  void CreateChildren() {
    for (auto& child : children_) {
      action_item_->AddChild(std::move(*child).Build());
    }
  }
  [[nodiscard]] std::unique_ptr<BuilderT> Release() {
    return std::make_unique<BuilderT>(std::move(static_cast<BuilderT&>(*this)));
  }

  // Owned and meaningful during the Builder building process. Its
  // ownership will be transferred out upon Build() call.
  std::unique_ptr<ActionItemClass> action_item_;
  ChildList children_;
};

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 BaseActionItemBuilderT<ActionItemBuilder, ActionItem> {
    // TODO: possibly construct a Core class of
    // ctors to avoid writing this in every derived
    // class.
    using BaseActionItemBuilderT::BaseActionItemBuilderT;
  };

  ActionItem();
  explicit ActionItem(InvokeActionCallback callback);
  ActionItem(const ActionItem&) = delete;
  ActionItem& operator=(const ActionItem&) = delete;
  ~ActionItem() override;

  // Build an action.
  static ActionItemBuilder Builder() { return ActionItemBuilder(); }
  static ActionItemBuilder Builder(InvokeActionCallback callback) {
    return ActionItemBuilder(std::move(callback));
  }

  // Configure action states and attributes.
  std::u16string_view GetAccessibleName() const;
  void SetAccessibleName(std::u16string_view accessible_name);
  std::optional<ActionId> GetActionId() const;
  void SetActionId(std::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);
  std::optional<int> GetGroupId() const;
  void SetGroupId(std::optional<int> group_id);
  const ui::ImageModel& GetImage() const;
  void SetImage(const ui::ImageModel& image);
  std::u16string_view GetText() const;
  void SetText(std::u16string_view text);
  std::u16string_view GetTooltipText() const;
  void SetTooltipText(std::u16string_view tooltip);
  bool GetVisible() const;
  void SetVisible(bool visible);
  void SetInvokeActionCallback(InvokeActionCallback callback);
  bool GetIsShowingBubble() const;
  void SetIsShowingBubble(bool showing_bubble);

  [[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();

  // ui::PropertyHandler:
  void AfterPropertyChange(const void* key, int64_t old_value) override;

  // Invoke an action.
  void InvokeAction(
      ActionInvocationContext context = ActionInvocationContext());

  // Get action metrics.
  int GetInvokeCount() const;
  std::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_;
  std::optional<ActionId> action_id_;
  ui::Accelerator accelerator_;
  bool checked_ = false;
  bool enabled_ = true;
  std::optional<int> group_id_;
  bool visible_ = true;
  std::u16string text_;
  std::u16string tooltip_;
  ui::ImageModel image_;
  Synonyms synonyms_;
  InvokeActionCallback callback_;
  int invoke_count_ = 0;
  std::optional<base::TimeTicks> last_invoke_time_;
  // Represents whether this action is currently showing associated ephemeral
  // UI. Pinned action buttons which execute on mouse release won't execute if
  // `is_showing_bubble_` was true on mouse press. Used to avoid immediately
  // re-triggering actions when mouse press was intended to dismiss their
  // ephemeral UI.
  // TODO(b/361251892): Rename this to appropriately reflect bubbles that do not
  // close on deactivate.
  bool is_showing_bubble_ = false;
  base::WeakPtrFactory<ActionItem> weak_ptr_factory_{this};
};

// TODO(crbug.com/375261318): Make it so that this ActionItem descendant can
// also be subclassed along with the builder.
// A subclass of ActionItem that has an additional image that reflects the
// current state of the action.
class COMPONENT_EXPORT(ACTIONS) StatefulImageActionItem : public ActionItem {
  METADATA_HEADER(StatefulImageActionItem, ActionItem)

 public:
  using ActionItem::ActionItem;
  ~StatefulImageActionItem() override;

  class StatefulImageActionItemBuilder
      : public BaseActionItemBuilderT<StatefulImageActionItemBuilder,
                                      StatefulImageActionItem> {
   public:
    using BaseActionItemBuilderT::BaseActionItemBuilderT;

    StatefulImageActionItemBuilder& SetStatefulImage(
        const ui::ImageModel& image) & {
      action_item_->SetStatefulImage(image);
      return *this;
    }

    StatefulImageActionItemBuilder&& SetStatefulImage(
        const ui::ImageModel& image) && {
      return std::move(this->SetStatefulImage(image));
    }
  };

  static StatefulImageActionItemBuilder Builder() {
    return StatefulImageActionItemBuilder();
  }
  static StatefulImageActionItemBuilder Builder(InvokeActionCallback callback) {
    return StatefulImageActionItemBuilder(std::move(callback));
  }

  const ui::ImageModel& GetStatefulImage() const;
  void SetStatefulImage(const ui::ImageModel& image);

 private:
  ui::ImageModel stateful_image_;
};

template <typename A>
bool IsActionItemClass(ActionItem* action_item) {
  return ui::metadata::IsClass<A, ActionItem>(action_item);
}

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 std::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 std::optional<ActionId> StringToActionId(
      const std::string action_id_string);
  static std::vector<std::optional<std::string>> ActionIdsToStrings(
      std::vector<ActionId> action_ids);
  static std::vector<std::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 std::optional<ActionIdToStringMap>& GetGlobalActionIdToStringMap();
  static std::optional<StringToActionIdMap>& GetGlobalStringToActionIdMap();
  static ActionIdToStringMap& GetActionIdToStringMap();
  static StringToActionIdMap& GetStringToActionIdMap();
};

enum class ActionPinnableState {
  kNotPinnable = 0,
  kPinnable = 1,
  kEnterpriseControlled = 2,
};

COMPONENT_EXPORT(ACTIONS)
extern const ui::ClassProperty<
    std::underlying_type_t<ActionPinnableState>>* const kActionItemPinnableKey;

}  // namespace actions

#endif  // UI_ACTIONS_ACTIONS_H_