910e62b5创建于 1月15日历史提交
// Copyright 2012 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/views/controls/menu/menu_model_adapter.h"

#include <memory>
#include <string>
#include <utility>
#include <vector>

#include "base/memory/raw_ptr.h"
#include "base/strings/utf_string_conversions.h"
#include "ui/base/models/menu_model.h"
#include "ui/base/models/menu_model_delegate.h"
#include "ui/views/controls/menu/menu_item_view.h"
#include "ui/views/controls/menu/menu_runner.h"
#include "ui/views/controls/menu/submenu_view.h"
#include "ui/views/test/views_test_base.h"

namespace {

// Base command id for test menu and its submenu.
constexpr int kRootIdBase = 100;
constexpr int kSubmenuIdBase = 200;
constexpr int kActionableSubmenuIdBase = 300;

class MenuModelBase : public ui::MenuModel {
 public:
  explicit MenuModelBase(int command_id_base)
      : command_id_base_(command_id_base) {}

  MenuModelBase(const MenuModelBase&) = delete;
  MenuModelBase& operator=(const MenuModelBase&) = delete;

  ~MenuModelBase() override = default;

  // ui::MenuModel implementation:
  size_t GetItemCount() const override { return items_.size(); }

  ItemType GetTypeAt(size_t index) const override { return items_[index].type; }

  ui::MenuSeparatorType GetSeparatorTypeAt(size_t index) const override {
    return ui::NORMAL_SEPARATOR;
  }

  int GetCommandIdAt(size_t index) const override {
    return static_cast<int>(index) + command_id_base_;
  }

  std::u16string GetLabelAt(size_t index) const override {
    return items_[index].label;
  }

  bool IsItemDynamicAt(size_t index) const override { return false; }

  bool GetAcceleratorAt(size_t index,
                        ui::Accelerator* accelerator) const override {
    return false;
  }

  bool IsItemCheckedAt(size_t index) const override { return false; }

  int GetGroupIdAt(size_t index) const override { return 0; }

  ui::ImageModel GetIconAt(size_t index) const override {
    return ui::ImageModel();
  }

  ui::ButtonMenuItemModel* GetButtonMenuItemAt(size_t index) const override {
    return nullptr;
  }

  bool IsEnabledAt(size_t index) const override {
    return items_[index].enabled;
  }

  bool IsVisibleAt(size_t index) const override {
    return items_[index].visible;
  }

  bool IsAlertedAt(size_t index) const override {
    return items_[index].alerted;
  }

  bool IsNewFeatureAt(size_t index) const override {
    return items_[index].new_feature;
  }

  MenuModel* GetSubmenuModelAt(size_t index) const override {
    return items_[index].submenu;
  }

  void ActivatedAt(size_t index) override { set_last_activation(index); }

  void ActivatedAt(size_t index, int event_flags) override {
    ActivatedAt(index);
  }

  void MenuWillShow() override {}

  void MenuWillClose() override {}

  // Item definition.
  struct Item {
    Item(ItemType item_type,
         const std::string& item_label,
         ui::MenuModel* item_submenu)
        : type(item_type),
          label(base::ASCIIToUTF16(item_label)),
          submenu(item_submenu) {}

    Item(ItemType item_type,
         const std::string& item_label,
         ui::MenuModel* item_submenu,
         bool enabled,
         bool visible)
        : type(item_type),
          label(base::ASCIIToUTF16(item_label)),
          submenu(item_submenu),
          enabled(enabled),
          visible(visible) {}

    ItemType type;
    std::u16string label;
    raw_ptr<ui::MenuModel> submenu;
    bool enabled = true;
    bool visible = true;
    bool alerted = false;
    bool new_feature = false;
  };

  const Item& GetItemDefinition(size_t index) { return items_[index]; }

  // Access index argument to ActivatedAt().
  std::optional<size_t> last_activation() const { return last_activation_; }
  void set_last_activation(std::optional<size_t> last_activation) {
    last_activation_ = last_activation;
  }

 protected:
  std::vector<Item> items_;

 private:
  int command_id_base_;
  std::optional<size_t> last_activation_;
};

class SubmenuModel final : public MenuModelBase {
 public:
  SubmenuModel() : MenuModelBase(kSubmenuIdBase) {
    items_.emplace_back(TYPE_COMMAND, "submenu item 0", nullptr, false, true);
    items_.emplace_back(TYPE_COMMAND, "submenu item 1", nullptr);
    items_[1].alerted = true;
  }

  SubmenuModel(const SubmenuModel&) = delete;
  SubmenuModel& operator=(const SubmenuModel&) = delete;

  ~SubmenuModel() override = default;

  base::WeakPtr<ui::MenuModel> AsWeakPtr() override {
    return weak_ptr_factory_.GetWeakPtr();
  }

 private:
  base::WeakPtrFactory<SubmenuModel> weak_ptr_factory_{this};
};

class ActionableSubmenuModel final : public MenuModelBase {
 public:
  ActionableSubmenuModel() : MenuModelBase(kActionableSubmenuIdBase) {
    items_.emplace_back(TYPE_COMMAND, "actionable submenu item 0", nullptr);
    items_.emplace_back(TYPE_COMMAND, "actionable submenu item 1", nullptr);
    items_[1].new_feature = true;
  }

  ActionableSubmenuModel(const ActionableSubmenuModel&) = delete;
  ActionableSubmenuModel& operator=(const ActionableSubmenuModel&) = delete;

  ~ActionableSubmenuModel() override = default;

  base::WeakPtr<ui::MenuModel> AsWeakPtr() override {
    return weak_ptr_factory_.GetWeakPtr();
  }

 private:
  base::WeakPtrFactory<ActionableSubmenuModel> weak_ptr_factory_{this};
};

class RootModel final : public MenuModelBase {
 public:
  RootModel() : MenuModelBase(kRootIdBase) {
    submenu_model_ = std::make_unique<SubmenuModel>();
    actionable_submenu_model_ = std::make_unique<ActionableSubmenuModel>();

    items_.emplace_back(TYPE_COMMAND, "command 0", nullptr, false, false);
    items_.emplace_back(TYPE_CHECK, "check 1", nullptr);
    items_.emplace_back(TYPE_SEPARATOR, "", nullptr);
    items_.emplace_back(TYPE_SUBMENU, "submenu 3", submenu_model_.get());
    items_.emplace_back(TYPE_RADIO, "radio 4", nullptr);
    items_.emplace_back(TYPE_ACTIONABLE_SUBMENU, "actionable 5",
                        actionable_submenu_model_.get());
  }

  RootModel(const RootModel&) = delete;
  RootModel& operator=(const RootModel&) = delete;

  ~RootModel() override {
    // Avoid that the pointer to `submenu_model_` becomes dangling.
    items_.clear();
  }

  base::WeakPtr<ui::MenuModel> AsWeakPtr() override {
    return weak_ptr_factory_.GetWeakPtr();
  }

 private:
  std::unique_ptr<MenuModel> submenu_model_;
  std::unique_ptr<MenuModel> actionable_submenu_model_;
  base::WeakPtrFactory<RootModel> weak_ptr_factory_{this};
};

void CheckSubmenu(const RootModel& model,
                  views::MenuItemView* menu,
                  views::MenuModelAdapter* delegate,
                  int submenu_id,
                  size_t expected_children,
                  size_t submenu_model_index,
                  int id) {
  views::MenuItemView* submenu = menu->GetMenuItemByID(submenu_id);
  views::SubmenuView* subitem_container = submenu->GetSubmenu();
  EXPECT_EQ(expected_children, subitem_container->children().size());

  MenuModelBase* submodel =
      static_cast<MenuModelBase*>(model.GetSubmenuModelAt(submenu_model_index));
  EXPECT_TRUE(submodel);
  for (size_t i = 0; i < subitem_container->children().size(); ++i, ++id) {
    const MenuModelBase::Item& model_item = submodel->GetItemDefinition(i);

    views::MenuItemView* item = menu->GetMenuItemByID(id);
    if (!item) {
      EXPECT_EQ(ui::MenuModel::TYPE_SEPARATOR, model_item.type);
      continue;
    }
    // Check placement.
    EXPECT_EQ(i, submenu->GetSubmenu()->GetIndexOf(item));

    // Check type.
    switch (model_item.type) {
      case ui::MenuModel::TYPE_TITLE:
        EXPECT_EQ(views::MenuItemView::Type::kTitle, item->GetType());
        break;
      case ui::MenuModel::TYPE_COMMAND:
        EXPECT_EQ(views::MenuItemView::Type::kNormal, item->GetType());
        break;
      case ui::MenuModel::TYPE_CHECK:
        EXPECT_EQ(views::MenuItemView::Type::kCheckbox, item->GetType());
        break;
      case ui::MenuModel::TYPE_RADIO:
        EXPECT_EQ(views::MenuItemView::Type::kRadio, item->GetType());
        break;
      case ui::MenuModel::TYPE_SEPARATOR:
      case ui::MenuModel::TYPE_BUTTON_ITEM:
        break;
      case ui::MenuModel::TYPE_SUBMENU:
        EXPECT_EQ(views::MenuItemView::Type::kSubMenu, item->GetType());
        break;
      case ui::MenuModel::TYPE_ACTIONABLE_SUBMENU:
        EXPECT_EQ(views::MenuItemView::Type::kActionableSubMenu,
                  item->GetType());
        break;
      case ui::MenuModel::TYPE_HIGHLIGHTED:
        EXPECT_EQ(views::MenuItemView::Type::kHighlighted, item->GetType());
        break;
    }

    // Check enabled state.
    EXPECT_EQ(model_item.enabled, item->GetEnabled());

    // Check visibility.
    EXPECT_EQ(model_item.visible, item->GetVisible());

    // Check alert state.
    EXPECT_EQ(model_item.alerted, item->is_alerted());

    // Check new feature flag.
    EXPECT_EQ(model_item.new_feature, item->is_new());

    // Check activation.
    static_cast<views::MenuDelegate*>(delegate)->ExecuteCommand(id);
    EXPECT_EQ(i, submodel->last_activation());
    submodel->set_last_activation(std::nullopt);
  }
}

}  // namespace

namespace views {

using MenuModelAdapterTest = ViewsTestBase;

TEST_F(MenuModelAdapterTest, BasicTest) {
  // Build model and adapter.
  RootModel model;
  views::MenuModelAdapter delegate(&model);

  // Create menu.  Build menu twice to check that rebuilding works properly.
  auto menu_owning = std::make_unique<MenuItemView>(&delegate);
  MenuItemView* menu = menu_owning.get();
  MenuRunner menu_runner(std::move(menu_owning), 0);
  delegate.BuildMenu(menu);
  delegate.BuildMenu(menu);
  EXPECT_TRUE(menu->HasSubmenu());

  // Check top level menu items.
  views::SubmenuView* item_container = menu->GetSubmenu();
  EXPECT_EQ(6u, item_container->children().size());

  int id = kRootIdBase;
  for (size_t i = 0; i < item_container->children().size(); ++i, ++id) {
    const MenuModelBase::Item& model_item = model.GetItemDefinition(i);

    MenuItemView* item = menu->GetMenuItemByID(id);
    if (!item) {
      EXPECT_EQ(ui::MenuModel::TYPE_SEPARATOR, model_item.type);
      continue;
    }

    // Check placement.
    EXPECT_EQ(i, menu->GetSubmenu()->GetIndexOf(item));

    // Check type.
    switch (model_item.type) {
      case ui::MenuModel::TYPE_TITLE:
        EXPECT_EQ(views::MenuItemView::Type::kTitle, item->GetType());
        break;
      case ui::MenuModel::TYPE_COMMAND:
        EXPECT_EQ(views::MenuItemView::Type::kNormal, item->GetType());
        break;
      case ui::MenuModel::TYPE_CHECK:
        EXPECT_EQ(views::MenuItemView::Type::kCheckbox, item->GetType());
        break;
      case ui::MenuModel::TYPE_RADIO:
        EXPECT_EQ(views::MenuItemView::Type::kRadio, item->GetType());
        break;
      case ui::MenuModel::TYPE_SEPARATOR:
      case ui::MenuModel::TYPE_BUTTON_ITEM:
        break;
      case ui::MenuModel::TYPE_SUBMENU:
        EXPECT_EQ(views::MenuItemView::Type::kSubMenu, item->GetType());
        break;
      case ui::MenuModel::TYPE_ACTIONABLE_SUBMENU:
        EXPECT_EQ(views::MenuItemView::Type::kActionableSubMenu,
                  item->GetType());
        break;
      case ui::MenuModel::TYPE_HIGHLIGHTED:
        EXPECT_EQ(views::MenuItemView::Type::kHighlighted, item->GetType());
        break;
    }

    // Check enabled state.
    EXPECT_EQ(model_item.enabled, item->GetEnabled());

    // Check visibility.
    EXPECT_EQ(model_item.visible, item->GetVisible());

    // Check alert state.
    EXPECT_EQ(model_item.alerted, item->is_alerted());

    // Check new feature flag.
    EXPECT_EQ(model_item.new_feature, item->is_new());

    // Check activation.
    static_cast<views::MenuDelegate*>(&delegate)->ExecuteCommand(id);
    EXPECT_EQ(i, model.last_activation());
    model.set_last_activation(std::nullopt);
  }

  // Check the submenu.
  const int submenu_index = 3;
  CheckSubmenu(model, menu, &delegate, kRootIdBase + submenu_index, 2,
               submenu_index, kSubmenuIdBase);

  // Check the actionable submenu.
  const int actionable_submenu_index = 5;
  CheckSubmenu(model, menu, &delegate, kRootIdBase + actionable_submenu_index,
               2, actionable_submenu_index, kActionableSubmenuIdBase);
}

}  // namespace views