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/base/models/simple_menu_model.h"
6
7#include "base/bind.h"
8#include "base/message_loop/message_loop.h"
9#include "ui/base/l10n/l10n_util.h"
10#include "ui/gfx/image/image.h"
11
12namespace ui {
13
14const int kSeparatorId = -1;
15
16struct SimpleMenuModel::Item {
17  int command_id;
18  base::string16 label;
19  base::string16 sublabel;
20  base::string16 minor_text;
21  gfx::Image icon;
22  ItemType type;
23  int group_id;
24  MenuModel* submenu;
25  ButtonMenuItemModel* button_model;
26  MenuSeparatorType separator_type;
27};
28
29////////////////////////////////////////////////////////////////////////////////
30// SimpleMenuModel::Delegate, public:
31
32bool SimpleMenuModel::Delegate::IsCommandIdVisible(int command_id) const {
33  return true;
34}
35
36bool SimpleMenuModel::Delegate::IsItemForCommandIdDynamic(
37    int command_id) const {
38  return false;
39}
40
41base::string16 SimpleMenuModel::Delegate::GetLabelForCommandId(
42    int command_id) const {
43  return base::string16();
44}
45
46base::string16 SimpleMenuModel::Delegate::GetSublabelForCommandId(
47    int command_id) const {
48  return base::string16();
49}
50
51base::string16 SimpleMenuModel::Delegate::GetMinorTextForCommandId(
52    int command_id) const {
53  return base::string16();
54}
55
56bool SimpleMenuModel::Delegate::GetIconForCommandId(
57    int command_id, gfx::Image* image_skia) const {
58  return false;
59}
60
61void SimpleMenuModel::Delegate::CommandIdHighlighted(int command_id) {
62}
63
64void SimpleMenuModel::Delegate::ExecuteCommand(
65    int command_id, int event_flags) {
66  ExecuteCommand(command_id, event_flags);
67}
68
69void SimpleMenuModel::Delegate::MenuWillShow(SimpleMenuModel* /*source*/) {
70}
71
72void SimpleMenuModel::Delegate::MenuClosed(SimpleMenuModel* /*source*/) {
73}
74
75////////////////////////////////////////////////////////////////////////////////
76// SimpleMenuModel, public:
77
78SimpleMenuModel::SimpleMenuModel(Delegate* delegate)
79    : delegate_(delegate),
80      menu_model_delegate_(NULL),
81      method_factory_(this) {
82}
83
84SimpleMenuModel::~SimpleMenuModel() {
85}
86
87void SimpleMenuModel::AddItem(int command_id, const base::string16& label) {
88  Item item = { command_id, label, base::string16(), base::string16(),
89                gfx::Image(), TYPE_COMMAND, -1, NULL, NULL, NORMAL_SEPARATOR };
90  AppendItem(item);
91}
92
93void SimpleMenuModel::AddItemWithStringId(int command_id, int string_id) {
94  AddItem(command_id, l10n_util::GetStringUTF16(string_id));
95}
96
97void SimpleMenuModel::AddCheckItem(int command_id,
98                                   const base::string16& label) {
99  Item item = { command_id, label, base::string16(), base::string16(),
100                gfx::Image(), TYPE_CHECK, -1, NULL, NULL, NORMAL_SEPARATOR };
101  AppendItem(item);
102}
103
104void SimpleMenuModel::AddCheckItemWithStringId(int command_id, int string_id) {
105  AddCheckItem(command_id, l10n_util::GetStringUTF16(string_id));
106}
107
108void SimpleMenuModel::AddRadioItem(int command_id,
109                                   const base::string16& label,
110                                   int group_id) {
111  Item item = { command_id, label, base::string16(), base::string16(),
112                gfx::Image(), TYPE_RADIO, group_id, NULL, NULL,
113                NORMAL_SEPARATOR };
114  AppendItem(item);
115}
116
117void SimpleMenuModel::AddRadioItemWithStringId(int command_id, int string_id,
118                                               int group_id) {
119  AddRadioItem(command_id, l10n_util::GetStringUTF16(string_id), group_id);
120}
121
122void SimpleMenuModel::AddSeparator(MenuSeparatorType separator_type) {
123  if (items_.empty()) {
124    if (separator_type == NORMAL_SEPARATOR) {
125      return;
126    }
127    DCHECK_EQ(SPACING_SEPARATOR, separator_type);
128  } else if (items_.back().type == TYPE_SEPARATOR) {
129    DCHECK_EQ(NORMAL_SEPARATOR, separator_type);
130    DCHECK_EQ(NORMAL_SEPARATOR, items_.back().separator_type);
131    return;
132  }
133#if !defined(USE_AURA)
134  if (separator_type == SPACING_SEPARATOR)
135    NOTIMPLEMENTED();
136#endif
137  Item item = { kSeparatorId, base::string16(), base::string16(),
138                base::string16(), gfx::Image(), TYPE_SEPARATOR, -1, NULL, NULL,
139                separator_type };
140  AppendItem(item);
141}
142
143void SimpleMenuModel::RemoveTrailingSeparators() {
144  while (!items_.empty() && items_.back().type == TYPE_SEPARATOR)
145    items_.pop_back();
146  MenuItemsChanged();
147}
148
149void SimpleMenuModel::AddButtonItem(int command_id,
150                                    ButtonMenuItemModel* model) {
151  Item item = { command_id, base::string16(), base::string16(),
152                base::string16(), gfx::Image(), TYPE_BUTTON_ITEM, -1, NULL,
153                model, NORMAL_SEPARATOR };
154  AppendItem(item);
155}
156
157void SimpleMenuModel::AddSubMenu(int command_id,
158                                 const base::string16& label,
159                                 MenuModel* model) {
160  Item item = { command_id, label, base::string16(), base::string16(),
161                gfx::Image(), TYPE_SUBMENU, -1, model, NULL, NORMAL_SEPARATOR };
162  AppendItem(item);
163}
164
165void SimpleMenuModel::AddSubMenuWithStringId(int command_id,
166                                             int string_id, MenuModel* model) {
167  AddSubMenu(command_id, l10n_util::GetStringUTF16(string_id), model);
168}
169
170void SimpleMenuModel::InsertItemAt(int index,
171                                   int command_id,
172                                   const base::string16& label) {
173  Item item = { command_id, label, base::string16(), base::string16(),
174                gfx::Image(), TYPE_COMMAND, -1, NULL, NULL, NORMAL_SEPARATOR };
175  InsertItemAtIndex(item, index);
176}
177
178void SimpleMenuModel::InsertItemWithStringIdAt(
179    int index, int command_id, int string_id) {
180  InsertItemAt(index, command_id, l10n_util::GetStringUTF16(string_id));
181}
182
183void SimpleMenuModel::InsertSeparatorAt(int index,
184                                        MenuSeparatorType separator_type) {
185#if !defined(USE_AURA)
186  if (separator_type != NORMAL_SEPARATOR) {
187    NOTIMPLEMENTED();
188  }
189#endif
190  Item item = { kSeparatorId, base::string16(), base::string16(),
191                base::string16(), gfx::Image(), TYPE_SEPARATOR, -1, NULL, NULL,
192                separator_type };
193  InsertItemAtIndex(item, index);
194}
195
196void SimpleMenuModel::InsertCheckItemAt(int index,
197                                        int command_id,
198                                        const base::string16& label) {
199  Item item = { command_id, label, base::string16(), base::string16(),
200                gfx::Image(), TYPE_CHECK, -1, NULL, NULL, NORMAL_SEPARATOR };
201  InsertItemAtIndex(item, index);
202}
203
204void SimpleMenuModel::InsertCheckItemWithStringIdAt(
205    int index, int command_id, int string_id) {
206  InsertCheckItemAt(index, command_id, l10n_util::GetStringUTF16(string_id));
207}
208
209void SimpleMenuModel::InsertRadioItemAt(int index,
210                                        int command_id,
211                                        const base::string16& label,
212                                        int group_id) {
213  Item item = { command_id, label, base::string16(), base::string16(),
214                gfx::Image(), TYPE_RADIO, group_id, NULL, NULL,
215                NORMAL_SEPARATOR };
216  InsertItemAtIndex(item, index);
217}
218
219void SimpleMenuModel::InsertRadioItemWithStringIdAt(
220    int index, int command_id, int string_id, int group_id) {
221  InsertRadioItemAt(
222      index, command_id, l10n_util::GetStringUTF16(string_id), group_id);
223}
224
225void SimpleMenuModel::InsertSubMenuAt(int index,
226                                      int command_id,
227                                      const base::string16& label,
228                                      MenuModel* model) {
229  Item item = { command_id, label, base::string16(), base::string16(),
230                gfx::Image(), TYPE_SUBMENU, -1, model, NULL,
231                NORMAL_SEPARATOR };
232  InsertItemAtIndex(item, index);
233}
234
235void SimpleMenuModel::InsertSubMenuWithStringIdAt(
236    int index, int command_id, int string_id, MenuModel* model) {
237  InsertSubMenuAt(index, command_id, l10n_util::GetStringUTF16(string_id),
238                  model);
239}
240
241void SimpleMenuModel::RemoveItemAt(int index) {
242  items_.erase(items_.begin() + ValidateItemIndex(index));
243  MenuItemsChanged();
244}
245
246void SimpleMenuModel::SetIcon(int index, const gfx::Image& icon) {
247  items_[ValidateItemIndex(index)].icon = icon;
248  MenuItemsChanged();
249}
250
251void SimpleMenuModel::SetSublabel(int index, const base::string16& sublabel) {
252  items_[ValidateItemIndex(index)].sublabel = sublabel;
253  MenuItemsChanged();
254}
255
256void SimpleMenuModel::SetMinorText(int index,
257                                   const base::string16& minor_text) {
258  items_[ValidateItemIndex(index)].minor_text = minor_text;
259}
260
261void SimpleMenuModel::Clear() {
262  items_.clear();
263  MenuItemsChanged();
264}
265
266int SimpleMenuModel::GetIndexOfCommandId(int command_id) {
267  for (ItemVector::iterator i = items_.begin(); i != items_.end(); ++i) {
268    if (i->command_id == command_id)
269      return static_cast<int>(std::distance(items_.begin(), i));
270  }
271  return -1;
272}
273
274////////////////////////////////////////////////////////////////////////////////
275// SimpleMenuModel, MenuModel implementation:
276
277bool SimpleMenuModel::HasIcons() const {
278  for (ItemVector::const_iterator i = items_.begin(); i != items_.end(); ++i) {
279    if (!i->icon.IsEmpty())
280      return true;
281  }
282
283  return false;
284}
285
286int SimpleMenuModel::GetItemCount() const {
287  return static_cast<int>(items_.size());
288}
289
290MenuModel::ItemType SimpleMenuModel::GetTypeAt(int index) const {
291  return items_[ValidateItemIndex(index)].type;
292}
293
294ui::MenuSeparatorType SimpleMenuModel::GetSeparatorTypeAt(int index) const {
295  return items_[ValidateItemIndex(index)].separator_type;
296}
297
298int SimpleMenuModel::GetCommandIdAt(int index) const {
299  return items_[ValidateItemIndex(index)].command_id;
300}
301
302base::string16 SimpleMenuModel::GetLabelAt(int index) const {
303  if (IsItemDynamicAt(index))
304    return delegate_->GetLabelForCommandId(GetCommandIdAt(index));
305  return items_[ValidateItemIndex(index)].label;
306}
307
308base::string16 SimpleMenuModel::GetSublabelAt(int index) const {
309  if (IsItemDynamicAt(index))
310    return delegate_->GetSublabelForCommandId(GetCommandIdAt(index));
311  return items_[ValidateItemIndex(index)].sublabel;
312}
313
314base::string16 SimpleMenuModel::GetMinorTextAt(int index) const {
315  if (IsItemDynamicAt(index))
316    return delegate_->GetMinorTextForCommandId(GetCommandIdAt(index));
317  return items_[ValidateItemIndex(index)].minor_text;
318}
319
320bool SimpleMenuModel::IsItemDynamicAt(int index) const {
321  if (delegate_)
322    return delegate_->IsItemForCommandIdDynamic(GetCommandIdAt(index));
323  return false;
324}
325
326bool SimpleMenuModel::GetAcceleratorAt(int index,
327                                       ui::Accelerator* accelerator) const {
328  if (delegate_) {
329    return delegate_->GetAcceleratorForCommandId(GetCommandIdAt(index),
330                                                 accelerator);
331  }
332  return false;
333}
334
335bool SimpleMenuModel::IsItemCheckedAt(int index) const {
336  if (!delegate_)
337    return false;
338  MenuModel::ItemType item_type = GetTypeAt(index);
339  return (item_type == TYPE_CHECK || item_type == TYPE_RADIO) ?
340      delegate_->IsCommandIdChecked(GetCommandIdAt(index)) : false;
341}
342
343int SimpleMenuModel::GetGroupIdAt(int index) const {
344  return items_[ValidateItemIndex(index)].group_id;
345}
346
347bool SimpleMenuModel::GetIconAt(int index, gfx::Image* icon) {
348  if (IsItemDynamicAt(index))
349    return delegate_->GetIconForCommandId(GetCommandIdAt(index), icon);
350
351  ValidateItemIndex(index);
352  if (items_[index].icon.IsEmpty())
353    return false;
354
355  *icon = items_[index].icon;
356  return true;
357}
358
359ButtonMenuItemModel* SimpleMenuModel::GetButtonMenuItemAt(int index) const {
360  return items_[ValidateItemIndex(index)].button_model;
361}
362
363bool SimpleMenuModel::IsEnabledAt(int index) const {
364  int command_id = GetCommandIdAt(index);
365  if (!delegate_ || command_id == kSeparatorId || GetButtonMenuItemAt(index))
366    return true;
367  return delegate_->IsCommandIdEnabled(command_id);
368}
369
370bool SimpleMenuModel::IsVisibleAt(int index) const {
371  int command_id = GetCommandIdAt(index);
372  if (!delegate_ || command_id == kSeparatorId || GetButtonMenuItemAt(index))
373    return true;
374  return delegate_->IsCommandIdVisible(command_id);
375}
376
377void SimpleMenuModel::HighlightChangedTo(int index) {
378  if (delegate_)
379    delegate_->CommandIdHighlighted(GetCommandIdAt(index));
380}
381
382void SimpleMenuModel::ActivatedAt(int index) {
383  if (delegate_)
384    delegate_->ExecuteCommand(GetCommandIdAt(index), 0);
385}
386
387void SimpleMenuModel::ActivatedAt(int index, int event_flags) {
388  if (delegate_)
389    delegate_->ExecuteCommand(GetCommandIdAt(index), event_flags);
390}
391
392MenuModel* SimpleMenuModel::GetSubmenuModelAt(int index) const {
393  return items_[ValidateItemIndex(index)].submenu;
394}
395
396void SimpleMenuModel::MenuWillShow() {
397  if (delegate_)
398    delegate_->MenuWillShow(this);
399}
400
401void SimpleMenuModel::MenuClosed() {
402  // Due to how menus work on the different platforms, ActivatedAt will be
403  // called after this.  It's more convenient for the delegate to be called
404  // afterwards though, so post a task.
405  base::MessageLoop::current()->PostTask(
406      FROM_HERE,
407      base::Bind(&SimpleMenuModel::OnMenuClosed, method_factory_.GetWeakPtr()));
408}
409
410void SimpleMenuModel::SetMenuModelDelegate(
411      ui::MenuModelDelegate* menu_model_delegate) {
412  menu_model_delegate_ = menu_model_delegate;
413}
414
415MenuModelDelegate* SimpleMenuModel::GetMenuModelDelegate() const {
416  return menu_model_delegate_;
417}
418
419void SimpleMenuModel::OnMenuClosed() {
420  if (delegate_)
421    delegate_->MenuClosed(this);
422}
423
424////////////////////////////////////////////////////////////////////////////////
425// SimpleMenuModel, Protected:
426
427void SimpleMenuModel::MenuItemsChanged() {
428}
429
430////////////////////////////////////////////////////////////////////////////////
431// SimpleMenuModel, Private:
432
433int SimpleMenuModel::ValidateItemIndex(int index) const {
434  CHECK_GE(index, 0);
435  CHECK_LT(static_cast<size_t>(index), items_.size());
436  return index;
437}
438
439void SimpleMenuModel::AppendItem(const Item& item) {
440  ValidateItem(item);
441  items_.push_back(item);
442  MenuItemsChanged();
443}
444
445void SimpleMenuModel::InsertItemAtIndex(const Item& item, int index) {
446  ValidateItem(item);
447  items_.insert(items_.begin() + index, item);
448  MenuItemsChanged();
449}
450
451void SimpleMenuModel::ValidateItem(const Item& item) {
452#ifndef NDEBUG
453  if (item.type == TYPE_SEPARATOR) {
454    DCHECK_EQ(item.command_id, kSeparatorId);
455  } else {
456    DCHECK_GE(item.command_id, 0);
457  }
458#endif  // NDEBUG
459}
460
461}  // namespace ui
462