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/logging.h"
8#include "ui/base/models/menu_model.h"
9#include "ui/gfx/image/image.h"
10#include "ui/views/controls/menu/menu_item_view.h"
11#include "ui/views/controls/menu/submenu_view.h"
12#include "ui/views/views_delegate.h"
13
14namespace views {
15
16MenuModelAdapter::MenuModelAdapter(ui::MenuModel* menu_model)
17    : menu_model_(menu_model),
18      triggerable_event_flags_(ui::EF_LEFT_MOUSE_BUTTON |
19                               ui::EF_RIGHT_MOUSE_BUTTON) {
20  DCHECK(menu_model);
21}
22
23MenuModelAdapter::~MenuModelAdapter() {
24}
25
26void MenuModelAdapter::BuildMenu(MenuItemView* menu) {
27  DCHECK(menu);
28
29  // Clear the menu.
30  if (menu->HasSubmenu()) {
31    const int subitem_count = menu->GetSubmenu()->child_count();
32    for (int i = 0; i < subitem_count; ++i)
33      menu->RemoveMenuItemAt(0);
34  }
35
36  // Leave entries in the map if the menu is being shown.  This
37  // allows the map to find the menu model of submenus being closed
38  // so ui::MenuModel::MenuClosed() can be called.
39  if (!menu->GetMenuController())
40    menu_map_.clear();
41  menu_map_[menu] = menu_model_;
42
43  // Repopulate the menu.
44  BuildMenuImpl(menu, menu_model_);
45  menu->ChildrenChanged();
46}
47
48MenuItemView* MenuModelAdapter::CreateMenu() {
49  MenuItemView* item = new MenuItemView(this);
50  BuildMenu(item);
51  return item;
52}
53
54// Static.
55MenuItemView* MenuModelAdapter::AddMenuItemFromModelAt(ui::MenuModel* model,
56                                                       int model_index,
57                                                       MenuItemView* menu,
58                                                       int menu_index,
59                                                       int item_id) {
60  gfx::Image icon;
61  model->GetIconAt(model_index, &icon);
62  base::string16 label, sublabel, minor_text;
63  ui::MenuSeparatorType separator_style = ui::NORMAL_SEPARATOR;
64  MenuItemView::Type type;
65  ui::MenuModel::ItemType menu_type = model->GetTypeAt(model_index);
66
67  switch (menu_type) {
68    case ui::MenuModel::TYPE_COMMAND:
69      type = MenuItemView::NORMAL;
70      label = model->GetLabelAt(model_index);
71      sublabel = model->GetSublabelAt(model_index);
72      minor_text = model->GetMinorTextAt(model_index);
73      break;
74    case ui::MenuModel::TYPE_CHECK:
75      type = MenuItemView::CHECKBOX;
76      label = model->GetLabelAt(model_index);
77      sublabel = model->GetSublabelAt(model_index);
78      minor_text = model->GetMinorTextAt(model_index);
79      break;
80    case ui::MenuModel::TYPE_RADIO:
81      type = MenuItemView::RADIO;
82      label = model->GetLabelAt(model_index);
83      sublabel = model->GetSublabelAt(model_index);
84      minor_text = model->GetMinorTextAt(model_index);
85      break;
86    case ui::MenuModel::TYPE_SEPARATOR:
87      icon = gfx::Image();
88      type = MenuItemView::SEPARATOR;
89      separator_style = model->GetSeparatorTypeAt(model_index);
90      break;
91    case ui::MenuModel::TYPE_SUBMENU:
92      type = MenuItemView::SUBMENU;
93      label = model->GetLabelAt(model_index);
94      sublabel = model->GetSublabelAt(model_index);
95      minor_text = model->GetMinorTextAt(model_index);
96      break;
97    default:
98      NOTREACHED();
99      type = MenuItemView::NORMAL;
100      break;
101  }
102
103  return menu->AddMenuItemAt(
104      menu_index,
105      item_id,
106      label,
107      sublabel,
108      minor_text,
109      icon.IsEmpty() ? gfx::ImageSkia() : *icon.ToImageSkia(),
110      type,
111      separator_style);
112}
113
114// Static.
115MenuItemView* MenuModelAdapter::AppendMenuItemFromModel(ui::MenuModel* model,
116                                                        int model_index,
117                                                        MenuItemView* menu,
118                                                        int item_id) {
119  const int menu_index = menu->HasSubmenu() ?
120      menu->GetSubmenu()->child_count() : 0;
121  return AddMenuItemFromModelAt(model, model_index, menu, menu_index, item_id);
122}
123
124
125MenuItemView* MenuModelAdapter::AppendMenuItem(MenuItemView* menu,
126                                               ui::MenuModel* model,
127                                               int index) {
128  return AppendMenuItemFromModel(model, index, menu,
129                                 model->GetCommandIdAt(index));
130}
131
132// MenuModelAdapter, MenuDelegate implementation:
133
134void MenuModelAdapter::ExecuteCommand(int id) {
135  ui::MenuModel* model = menu_model_;
136  int index = 0;
137  if (ui::MenuModel::GetModelAndIndexForCommandId(id, &model, &index)) {
138    model->ActivatedAt(index);
139    return;
140  }
141
142  NOTREACHED();
143}
144
145void MenuModelAdapter::ExecuteCommand(int id, int mouse_event_flags) {
146  ui::MenuModel* model = menu_model_;
147  int index = 0;
148  if (ui::MenuModel::GetModelAndIndexForCommandId(id, &model, &index)) {
149    model->ActivatedAt(index, mouse_event_flags);
150    return;
151  }
152
153  NOTREACHED();
154}
155
156bool MenuModelAdapter::IsTriggerableEvent(MenuItemView* source,
157                                          const ui::Event& e) {
158  return e.type() == ui::ET_GESTURE_TAP ||
159         e.type() == ui::ET_GESTURE_TAP_DOWN ||
160         (e.IsMouseEvent() && (triggerable_event_flags_ & e.flags()) != 0);
161}
162
163bool MenuModelAdapter::GetAccelerator(int id,
164                                      ui::Accelerator* accelerator) const {
165  ui::MenuModel* model = menu_model_;
166  int index = 0;
167  if (ui::MenuModel::GetModelAndIndexForCommandId(id, &model, &index))
168    return model->GetAcceleratorAt(index, accelerator);
169
170  NOTREACHED();
171  return false;
172}
173
174base::string16 MenuModelAdapter::GetLabel(int id) const {
175  ui::MenuModel* model = menu_model_;
176  int index = 0;
177  if (ui::MenuModel::GetModelAndIndexForCommandId(id, &model, &index))
178    return model->GetLabelAt(index);
179
180  NOTREACHED();
181  return base::string16();
182}
183
184const gfx::FontList* MenuModelAdapter::GetLabelFontList(int id) const {
185  ui::MenuModel* model = menu_model_;
186  int index = 0;
187  if (ui::MenuModel::GetModelAndIndexForCommandId(id, &model, &index)) {
188    const gfx::FontList* font_list = model->GetLabelFontListAt(index);
189    if (font_list)
190      return font_list;
191  }
192
193  // This line may be reached for the empty menu item.
194  return MenuDelegate::GetLabelFontList(id);
195}
196
197bool MenuModelAdapter::IsCommandEnabled(int id) const {
198  ui::MenuModel* model = menu_model_;
199  int index = 0;
200  if (ui::MenuModel::GetModelAndIndexForCommandId(id, &model, &index))
201    return model->IsEnabledAt(index);
202
203  NOTREACHED();
204  return false;
205}
206
207bool MenuModelAdapter::IsCommandVisible(int id) const {
208  ui::MenuModel* model = menu_model_;
209  int index = 0;
210  if (ui::MenuModel::GetModelAndIndexForCommandId(id, &model, &index))
211    return model->IsVisibleAt(index);
212
213  NOTREACHED();
214  return false;
215}
216
217bool MenuModelAdapter::IsItemChecked(int id) const {
218  ui::MenuModel* model = menu_model_;
219  int index = 0;
220  if (ui::MenuModel::GetModelAndIndexForCommandId(id, &model, &index))
221    return model->IsItemCheckedAt(index);
222
223  NOTREACHED();
224  return false;
225}
226
227void MenuModelAdapter::SelectionChanged(MenuItemView* menu) {
228  // Ignore selection of the root menu.
229  if (menu == menu->GetRootMenuItem())
230    return;
231
232  const int id = menu->GetCommand();
233  ui::MenuModel* model = menu_model_;
234  int index = 0;
235  if (ui::MenuModel::GetModelAndIndexForCommandId(id, &model, &index)) {
236    model->HighlightChangedTo(index);
237    return;
238  }
239
240  NOTREACHED();
241}
242
243void MenuModelAdapter::WillShowMenu(MenuItemView* menu) {
244  // Look up the menu model for this menu.
245  const std::map<MenuItemView*, ui::MenuModel*>::const_iterator map_iterator =
246      menu_map_.find(menu);
247  if (map_iterator != menu_map_.end()) {
248    map_iterator->second->MenuWillShow();
249    return;
250  }
251
252  NOTREACHED();
253}
254
255void MenuModelAdapter::WillHideMenu(MenuItemView* menu) {
256  // Look up the menu model for this menu.
257  const std::map<MenuItemView*, ui::MenuModel*>::const_iterator map_iterator =
258      menu_map_.find(menu);
259  if (map_iterator != menu_map_.end()) {
260    map_iterator->second->MenuClosed();
261    return;
262  }
263
264  NOTREACHED();
265}
266
267// MenuModelAdapter, private:
268
269void MenuModelAdapter::BuildMenuImpl(MenuItemView* menu, ui::MenuModel* model) {
270  DCHECK(menu);
271  DCHECK(model);
272  bool has_icons = model->HasIcons();
273  const int item_count = model->GetItemCount();
274  for (int i = 0; i < item_count; ++i) {
275    MenuItemView* item = AppendMenuItem(menu, model, i);
276
277    if (model->GetTypeAt(i) == ui::MenuModel::TYPE_SUBMENU) {
278      DCHECK(item);
279      DCHECK_EQ(MenuItemView::SUBMENU, item->GetType());
280      ui::MenuModel* submodel = model->GetSubmenuModelAt(i);
281      DCHECK(submodel);
282      BuildMenuImpl(item, submodel);
283      has_icons = has_icons || item->has_icons();
284
285      menu_map_[item] = submodel;
286    }
287  }
288
289  menu->set_has_icons(has_icons);
290}
291
292}  // namespace views
293