1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "base/strings/utf_string_conversions.h" 6 #include "ui/base/l10n/l10n_util.h" 7 #include "ui/base/models/menu_model.h" 8 #include "ui/base/models/menu_model_delegate.h" 9 #include "ui/views/controls/menu/menu_item_view.h" 10 #include "ui/views/controls/menu/menu_model_adapter.h" 11 #include "ui/views/controls/menu/menu_runner.h" 12 #include "ui/views/controls/menu/submenu_view.h" 13 #include "ui/views/test/views_test_base.h" 14 15 namespace { 16 17 // Base command id for test menu and its submenu. 18 const int kRootIdBase = 100; 19 const int kSubmenuIdBase = 200; 20 21 class MenuModelBase : public ui::MenuModel { 22 public: 23 explicit MenuModelBase(int command_id_base) 24 : command_id_base_(command_id_base), 25 last_activation_(-1) { 26 } 27 28 virtual ~MenuModelBase() { 29 } 30 31 // ui::MenuModel implementation: 32 33 virtual bool HasIcons() const OVERRIDE { 34 return false; 35 } 36 37 virtual int GetItemCount() const OVERRIDE { 38 return static_cast<int>(items_.size()); 39 } 40 41 virtual ItemType GetTypeAt(int index) const OVERRIDE { 42 return items_[index].type; 43 } 44 45 virtual ui::MenuSeparatorType GetSeparatorTypeAt( 46 int index) const OVERRIDE { 47 return ui::NORMAL_SEPARATOR; 48 } 49 50 virtual int GetCommandIdAt(int index) const OVERRIDE { 51 return index + command_id_base_; 52 } 53 54 virtual string16 GetLabelAt(int index) const OVERRIDE { 55 return items_[index].label; 56 } 57 58 virtual bool IsItemDynamicAt(int index) const OVERRIDE { 59 return false; 60 } 61 62 virtual const gfx::Font* GetLabelFontAt(int index) const OVERRIDE { 63 return NULL; 64 } 65 66 virtual bool GetAcceleratorAt(int index, 67 ui::Accelerator* accelerator) const OVERRIDE { 68 return false; 69 } 70 71 virtual bool IsItemCheckedAt(int index) const OVERRIDE { 72 return false; 73 } 74 75 virtual int GetGroupIdAt(int index) const OVERRIDE { 76 return 0; 77 } 78 79 virtual bool GetIconAt(int index, gfx::Image* icon) OVERRIDE { 80 return false; 81 } 82 83 virtual ui::ButtonMenuItemModel* GetButtonMenuItemAt( 84 int index) const OVERRIDE { 85 return NULL; 86 } 87 88 virtual bool IsEnabledAt(int index) const OVERRIDE { 89 return true; 90 } 91 92 virtual bool IsVisibleAt(int index) const OVERRIDE { 93 return true; 94 } 95 96 virtual MenuModel* GetSubmenuModelAt(int index) const OVERRIDE { 97 return items_[index].submenu; 98 } 99 100 virtual void HighlightChangedTo(int index) OVERRIDE { 101 } 102 103 virtual void ActivatedAt(int index) OVERRIDE { 104 set_last_activation(index); 105 } 106 107 virtual void ActivatedAt(int index, int event_flags) OVERRIDE { 108 ActivatedAt(index); 109 } 110 111 virtual void MenuWillShow() OVERRIDE { 112 } 113 114 virtual void MenuClosed() OVERRIDE { 115 } 116 117 virtual void SetMenuModelDelegate( 118 ui::MenuModelDelegate* delegate) OVERRIDE { 119 } 120 121 virtual ui::MenuModelDelegate* GetMenuModelDelegate() const OVERRIDE { 122 return NULL; 123 } 124 125 // Item definition. 126 struct Item { 127 Item(ItemType item_type, 128 const std::string& item_label, 129 ui::MenuModel* item_submenu) 130 : type(item_type), 131 label(ASCIIToUTF16(item_label)), 132 submenu(item_submenu) { 133 } 134 135 ItemType type; 136 string16 label; 137 ui::MenuModel* submenu; 138 }; 139 140 const Item& GetItemDefinition(int index) { 141 return items_[index]; 142 } 143 144 // Access index argument to ActivatedAt(). 145 int last_activation() const { return last_activation_; } 146 void set_last_activation(int last_activation) { 147 last_activation_ = last_activation; 148 } 149 150 protected: 151 std::vector<Item> items_; 152 153 private: 154 int command_id_base_; 155 int last_activation_; 156 157 DISALLOW_COPY_AND_ASSIGN(MenuModelBase); 158 }; 159 160 class SubmenuModel : public MenuModelBase { 161 public: 162 SubmenuModel() : MenuModelBase(kSubmenuIdBase) { 163 items_.push_back(Item(TYPE_COMMAND, "submenu item 0", NULL)); 164 items_.push_back(Item(TYPE_COMMAND, "submenu item 1", NULL)); 165 } 166 167 virtual ~SubmenuModel() { 168 } 169 170 private: 171 DISALLOW_COPY_AND_ASSIGN(SubmenuModel); 172 }; 173 174 class RootModel : public MenuModelBase { 175 public: 176 RootModel() : MenuModelBase(kRootIdBase) { 177 submenu_model_.reset(new SubmenuModel); 178 179 items_.push_back(Item(TYPE_COMMAND, "command 0", NULL)); 180 items_.push_back(Item(TYPE_CHECK, "check 1", NULL)); 181 items_.push_back(Item(TYPE_SEPARATOR, "", NULL)); 182 items_.push_back(Item(TYPE_SUBMENU, "submenu 3", submenu_model_.get())); 183 items_.push_back(Item(TYPE_RADIO, "radio 4", NULL)); 184 } 185 186 virtual ~RootModel() { 187 } 188 189 private: 190 scoped_ptr<MenuModel> submenu_model_; 191 192 DISALLOW_COPY_AND_ASSIGN(RootModel); 193 }; 194 195 } // namespace 196 197 namespace views { 198 199 typedef ViewsTestBase MenuModelAdapterTest; 200 201 TEST_F(MenuModelAdapterTest, BasicTest) { 202 // Build model and adapter. 203 RootModel model; 204 views::MenuModelAdapter delegate(&model); 205 206 // Create menu. Build menu twice to check that rebuilding works properly. 207 MenuItemView* menu = new views::MenuItemView(&delegate); 208 // MenuRunner takes ownership of menu. 209 scoped_ptr<MenuRunner> menu_runner(new MenuRunner(menu)); 210 delegate.BuildMenu(menu); 211 delegate.BuildMenu(menu); 212 EXPECT_TRUE(menu->HasSubmenu()); 213 214 // Check top level menu items. 215 views::SubmenuView* item_container = menu->GetSubmenu(); 216 EXPECT_EQ(5, item_container->child_count()); 217 218 for (int i = 0; i < item_container->child_count(); ++i) { 219 const MenuModelBase::Item& model_item = model.GetItemDefinition(i); 220 221 const int id = i + kRootIdBase; 222 MenuItemView* item = menu->GetMenuItemByID(id); 223 if (!item) { 224 EXPECT_EQ(ui::MenuModel::TYPE_SEPARATOR, model_item.type); 225 continue; 226 } 227 228 // Check placement. 229 EXPECT_EQ(i, menu->GetSubmenu()->GetIndexOf(item)); 230 231 // Check type. 232 switch (model_item.type) { 233 case ui::MenuModel::TYPE_COMMAND: 234 EXPECT_EQ(views::MenuItemView::NORMAL, item->GetType()); 235 break; 236 case ui::MenuModel::TYPE_CHECK: 237 EXPECT_EQ(views::MenuItemView::CHECKBOX, item->GetType()); 238 break; 239 case ui::MenuModel::TYPE_RADIO: 240 EXPECT_EQ(views::MenuItemView::RADIO, item->GetType()); 241 break; 242 case ui::MenuModel::TYPE_SEPARATOR: 243 case ui::MenuModel::TYPE_BUTTON_ITEM: 244 break; 245 case ui::MenuModel::TYPE_SUBMENU: 246 EXPECT_EQ(views::MenuItemView::SUBMENU, item->GetType()); 247 break; 248 } 249 250 // Check activation. 251 static_cast<views::MenuDelegate*>(&delegate)->ExecuteCommand(id); 252 EXPECT_EQ(i, model.last_activation()); 253 model.set_last_activation(-1); 254 } 255 256 // Check submenu items. 257 views::MenuItemView* submenu = menu->GetMenuItemByID(103); 258 views::SubmenuView* subitem_container = submenu->GetSubmenu(); 259 EXPECT_EQ(2, subitem_container->child_count()); 260 261 for (int i = 0; i < subitem_container->child_count(); ++i) { 262 MenuModelBase* submodel = static_cast<MenuModelBase*>( 263 model.GetSubmenuModelAt(3)); 264 EXPECT_TRUE(submodel); 265 266 const MenuModelBase::Item& model_item = submodel->GetItemDefinition(i); 267 268 const int id = i + kSubmenuIdBase; 269 MenuItemView* item = menu->GetMenuItemByID(id); 270 if (!item) { 271 EXPECT_EQ(ui::MenuModel::TYPE_SEPARATOR, model_item.type); 272 continue; 273 } 274 275 // Check placement. 276 EXPECT_EQ(i, submenu->GetSubmenu()->GetIndexOf(item)); 277 278 // Check type. 279 switch (model_item.type) { 280 case ui::MenuModel::TYPE_COMMAND: 281 EXPECT_EQ(views::MenuItemView::NORMAL, item->GetType()); 282 break; 283 case ui::MenuModel::TYPE_CHECK: 284 EXPECT_EQ(views::MenuItemView::CHECKBOX, item->GetType()); 285 break; 286 case ui::MenuModel::TYPE_RADIO: 287 EXPECT_EQ(views::MenuItemView::RADIO, item->GetType()); 288 break; 289 case ui::MenuModel::TYPE_SEPARATOR: 290 case ui::MenuModel::TYPE_BUTTON_ITEM: 291 break; 292 case ui::MenuModel::TYPE_SUBMENU: 293 EXPECT_EQ(views::MenuItemView::SUBMENU, item->GetType()); 294 break; 295 } 296 297 // Check activation. 298 static_cast<views::MenuDelegate*>(&delegate)->ExecuteCommand(id); 299 EXPECT_EQ(i, submodel->last_activation()); 300 submodel->set_last_activation(-1); 301 } 302 303 // Check that selecting the root item is safe. The MenuModel does 304 // not care about the root so MenuModelAdapter should do nothing 305 // (not hit the NOTREACHED check) when the root is selected. 306 static_cast<views::MenuDelegate*>(&delegate)->SelectionChanged(menu); 307 } 308 309 } // namespace views 310