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/models/menu_model.h"
9#include "ui/base/models/menu_model_delegate.h"
10#include "ui/views/controls/menu/menu_item_view.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
15namespace {
16
17// Base command id for test menu and its submenu.
18const int kRootIdBase = 100;
19const int kSubmenuIdBase = 200;
20
21class 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 base::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::FontList* GetLabelFontListAt(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(base::ASCIIToUTF16(item_label)),
132          submenu(item_submenu) {
133    }
134
135    ItemType type;
136    base::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
160class 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
174class 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
197namespace views {
198
199typedef ViewsTestBase MenuModelAdapterTest;
200
201TEST_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, 0));
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