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