// 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 "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. const int kRootIdBase = 100; const int kSubmenuIdBase = 200; class MenuModelBase : public ui::MenuModel { public: explicit MenuModelBase(int command_id_base) : command_id_base_(command_id_base), last_activation_(-1) { } ~MenuModelBase() override {} // ui::MenuModel implementation: bool HasIcons() const override { return false; } int GetItemCount() const override { return static_cast(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_; } base::string16 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 NULL; } 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; } bool GetIconAt(int index, gfx::Image* icon) override { return false; } ui::ButtonMenuItemModel* GetButtonMenuItemAt(int index) const override { return NULL; } bool IsEnabledAt(int index) const override { return true; } bool IsVisibleAt(int index) const override { return true; } MenuModel* GetSubmenuModelAt(int index) const override { return items_[index].submenu; } void HighlightChangedTo(int index) override {} void ActivatedAt(int index) override { set_last_activation(index); } void ActivatedAt(int index, int event_flags) override { ActivatedAt(index); } void MenuWillShow() override {} void MenuClosed() override {} void SetMenuModelDelegate(ui::MenuModelDelegate* delegate) override {} ui::MenuModelDelegate* GetMenuModelDelegate() const override { return NULL; } // 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) { } ItemType type; base::string16 label; ui::MenuModel* submenu; }; const Item& GetItemDefinition(int 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 items_; private: int command_id_base_; int last_activation_; DISALLOW_COPY_AND_ASSIGN(MenuModelBase); }; class SubmenuModel : public MenuModelBase { public: SubmenuModel() : MenuModelBase(kSubmenuIdBase) { items_.push_back(Item(TYPE_COMMAND, "submenu item 0", NULL)); items_.push_back(Item(TYPE_COMMAND, "submenu item 1", NULL)); } ~SubmenuModel() override {} private: DISALLOW_COPY_AND_ASSIGN(SubmenuModel); }; class RootModel : public MenuModelBase { public: RootModel() : MenuModelBase(kRootIdBase) { submenu_model_.reset(new SubmenuModel); items_.push_back(Item(TYPE_COMMAND, "command 0", NULL)); items_.push_back(Item(TYPE_CHECK, "check 1", NULL)); items_.push_back(Item(TYPE_SEPARATOR, "", NULL)); items_.push_back(Item(TYPE_SUBMENU, "submenu 3", submenu_model_.get())); items_.push_back(Item(TYPE_RADIO, "radio 4", NULL)); } ~RootModel() override {} private: scoped_ptr submenu_model_; DISALLOW_COPY_AND_ASSIGN(RootModel); }; } // namespace namespace views { typedef ViewsTestBase MenuModelAdapterTest; 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. scoped_ptr 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(5, item_container->child_count()); for (int i = 0; i < item_container->child_count(); ++i) { const MenuModelBase::Item& model_item = model.GetItemDefinition(i); const int id = i + kRootIdBase; 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_COMMAND: EXPECT_EQ(views::MenuItemView::NORMAL, item->GetType()); break; case ui::MenuModel::TYPE_CHECK: EXPECT_EQ(views::MenuItemView::CHECKBOX, item->GetType()); break; case ui::MenuModel::TYPE_RADIO: EXPECT_EQ(views::MenuItemView::RADIO, item->GetType()); break; case ui::MenuModel::TYPE_SEPARATOR: case ui::MenuModel::TYPE_BUTTON_ITEM: break; case ui::MenuModel::TYPE_SUBMENU: EXPECT_EQ(views::MenuItemView::SUBMENU, item->GetType()); break; } // Check activation. static_cast(&delegate)->ExecuteCommand(id); EXPECT_EQ(i, model.last_activation()); model.set_last_activation(-1); } // Check submenu items. views::MenuItemView* submenu = menu->GetMenuItemByID(103); views::SubmenuView* subitem_container = submenu->GetSubmenu(); EXPECT_EQ(2, subitem_container->child_count()); for (int i = 0; i < subitem_container->child_count(); ++i) { MenuModelBase* submodel = static_cast( model.GetSubmenuModelAt(3)); EXPECT_TRUE(submodel); const MenuModelBase::Item& model_item = submodel->GetItemDefinition(i); const int id = i + kSubmenuIdBase; 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_COMMAND: EXPECT_EQ(views::MenuItemView::NORMAL, item->GetType()); break; case ui::MenuModel::TYPE_CHECK: EXPECT_EQ(views::MenuItemView::CHECKBOX, item->GetType()); break; case ui::MenuModel::TYPE_RADIO: EXPECT_EQ(views::MenuItemView::RADIO, item->GetType()); break; case ui::MenuModel::TYPE_SEPARATOR: case ui::MenuModel::TYPE_BUTTON_ITEM: break; case ui::MenuModel::TYPE_SUBMENU: EXPECT_EQ(views::MenuItemView::SUBMENU, item->GetType()); break; } // Check activation. static_cast(&delegate)->ExecuteCommand(id); EXPECT_EQ(i, submodel->last_activation()); submodel->set_last_activation(-1); } // Check that selecting the root item is safe. The MenuModel does // not care about the root so MenuModelAdapter should do nothing // (not hit the NOTREACHED check) when the root is selected. static_cast(&delegate)->SelectionChanged(menu); } } // namespace views