blob: 7fb20b4a45f61388eb4259ac94044a7da93150c2 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// 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 <vector>
#include "base/macros.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), last_activation_(-1) {}
~MenuModelBase() override = default;
// ui::MenuModel implementation:
bool HasIcons() const override { return false; }
int GetItemCount() const override { return static_cast<int>(items_.size()); }
ItemType GetTypeAt(int index) const override { return items_[index].type; }
ui::MenuSeparatorType GetSeparatorTypeAt(int index) const override {
return ui::NORMAL_SEPARATOR;
}
int GetCommandIdAt(int index) const override {
return index + command_id_base_;
}
std::u16string GetLabelAt(int index) const override {
return items_[index].label;
}
bool IsItemDynamicAt(int index) const override { return false; }
const gfx::FontList* GetLabelFontListAt(int index) const override {
return nullptr;
}
bool GetAcceleratorAt(int index,
ui::Accelerator* accelerator) const override {
return false;
}
bool IsItemCheckedAt(int index) const override { return false; }
int GetGroupIdAt(int index) const override { return 0; }
ui::ImageModel GetIconAt(int index) const override {
return ui::ImageModel();
}
ui::ButtonMenuItemModel* GetButtonMenuItemAt(int index) const override {
return nullptr;
}
bool IsEnabledAt(int index) const override { return items_[index].enabled; }
bool IsVisibleAt(int index) const override { return items_[index].visible; }
bool IsAlertedAt(int index) const override { return items_[index].alerted; }
bool IsNewFeatureAt(int index) const override {
return items_[index].new_feature;
}
MenuModel* GetSubmenuModelAt(int index) const override {
return items_[index].submenu;
}
void ActivatedAt(int index) override { set_last_activation(index); }
void ActivatedAt(int 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),
enabled(true),
visible(true) {}
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;
ui::MenuModel* submenu;
bool enabled;
bool visible;
bool alerted = false;
bool new_feature = false;
};
const Item& GetItemDefinition(size_t index) { return items_[index]; }
// Access index argument to ActivatedAt().
int last_activation() const { return last_activation_; }
void set_last_activation(int last_activation) {
last_activation_ = last_activation;
}
protected:
std::vector<Item> items_;
private:
int command_id_base_;
int last_activation_;
DISALLOW_COPY_AND_ASSIGN(MenuModelBase);
};
class SubmenuModel : 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() override = default;
private:
DISALLOW_COPY_AND_ASSIGN(SubmenuModel);
};
class ActionableSubmenuModel : 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() override = default;
private:
DISALLOW_COPY_AND_ASSIGN(ActionableSubmenuModel);
};
class RootModel : 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() override = default;
private:
std::unique_ptr<MenuModel> submenu_model_;
std::unique_ptr<MenuModel> actionable_submenu_model_;
DISALLOW_COPY_AND_ASSIGN(RootModel);
};
void CheckSubmenu(const RootModel& model,
views::MenuItemView* menu,
views::MenuModelAdapter* delegate,
int submenu_id,
size_t expected_children,
int 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, size_t{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, size_t{submodel->last_activation()});
submodel->set_last_activation(-1);
}
}
} // 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.
MenuItemView* menu = new views::MenuItemView(&delegate);
// MenuRunner takes ownership of menu.
std::unique_ptr<MenuRunner> menu_runner(new MenuRunner(menu, 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, size_t{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, size_t{model.last_activation()});
model.set_last_activation(-1);
}
// 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