• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright (c) 2010 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#import "chrome/browser/ui/cocoa/menu_controller.h"
6
7#include "base/logging.h"
8#include "base/sys_string_conversions.h"
9#include "skia/ext/skia_utils_mac.h"
10#include "third_party/skia/include/core/SkBitmap.h"
11#include "ui/base/l10n/l10n_util_mac.h"
12#include "ui/base/models/accelerator_cocoa.h"
13#include "ui/base/models/simple_menu_model.h"
14
15@interface MenuController (Private)
16- (NSMenu*)menuFromModel:(ui::MenuModel*)model;
17- (void)addSeparatorToMenu:(NSMenu*)menu
18                   atIndex:(int)index;
19@end
20
21@implementation MenuController
22
23@synthesize model = model_;
24@synthesize useWithPopUpButtonCell = useWithPopUpButtonCell_;
25
26- (id)init {
27  self = [super init];
28  return self;
29}
30
31- (id)initWithModel:(ui::MenuModel*)model
32    useWithPopUpButtonCell:(BOOL)useWithCell {
33  if ((self = [super init])) {
34    model_ = model;
35    useWithPopUpButtonCell_ = useWithCell;
36    [self menu];
37  }
38  return self;
39}
40
41- (void)dealloc {
42  model_ = NULL;
43  [super dealloc];
44}
45
46// Creates a NSMenu from the given model. If the model has submenus, this can
47// be invoked recursively.
48- (NSMenu*)menuFromModel:(ui::MenuModel*)model {
49  NSMenu* menu = [[[NSMenu alloc] initWithTitle:@""] autorelease];
50
51  // The indices may not always start at zero (the windows system menu is one
52  // example where this is used) so just make sure we can handle it.
53  // SimpleMenuModel currently always starts at 0.
54  int firstItemIndex = model->GetFirstItemIndex(menu);
55  DCHECK(firstItemIndex == 0);
56  const int count = model->GetItemCount();
57  for (int index = firstItemIndex; index < firstItemIndex + count; index++) {
58    int modelIndex = index - firstItemIndex;
59    if (model->GetTypeAt(modelIndex) == ui::MenuModel::TYPE_SEPARATOR) {
60      [self addSeparatorToMenu:menu atIndex:index];
61    } else {
62      [self addItemToMenu:menu atIndex:index fromModel:model
63          modelIndex:modelIndex];
64    }
65  }
66
67  return menu;
68}
69
70// Adds a separator item at the given index. As the separator doesn't need
71// anything from the model, this method doesn't need the model index as the
72// other method below does.
73- (void)addSeparatorToMenu:(NSMenu*)menu
74                   atIndex:(int)index {
75  NSMenuItem* separator = [NSMenuItem separatorItem];
76  [menu insertItem:separator atIndex:index];
77}
78
79// Adds an item or a hierarchical menu to the item at the |index|,
80// associated with the entry in the model indentifed by |modelIndex|.
81- (void)addItemToMenu:(NSMenu*)menu
82              atIndex:(NSInteger)index
83            fromModel:(ui::MenuModel*)model
84           modelIndex:(int)modelIndex {
85  NSString* label =
86      l10n_util::FixUpWindowsStyleLabel(model->GetLabelAt(modelIndex));
87  scoped_nsobject<NSMenuItem> item(
88      [[NSMenuItem alloc] initWithTitle:label
89                                 action:@selector(itemSelected:)
90                          keyEquivalent:@""]);
91
92  // If the menu item has an icon, set it.
93  SkBitmap skiaIcon;
94  if (model->GetIconAt(modelIndex, &skiaIcon) && !skiaIcon.isNull()) {
95    NSImage* icon = gfx::SkBitmapToNSImage(skiaIcon);
96    if (icon) {
97      [item setImage:icon];
98    }
99  }
100
101  ui::MenuModel::ItemType type = model->GetTypeAt(modelIndex);
102  if (type == ui::MenuModel::TYPE_SUBMENU) {
103    // Recursively build a submenu from the sub-model at this index.
104    [item setTarget:nil];
105    [item setAction:nil];
106    ui::MenuModel* submenuModel = model->GetSubmenuModelAt(modelIndex);
107    NSMenu* submenu =
108        [self menuFromModel:(ui::SimpleMenuModel*)submenuModel];
109    [item setSubmenu:submenu];
110  } else {
111    // The MenuModel works on indexes so we can't just set the command id as the
112    // tag like we do in other menus. Also set the represented object to be
113    // the model so hierarchical menus check the correct index in the correct
114    // model. Setting the target to |self| allows this class to participate
115    // in validation of the menu items.
116    [item setTag:modelIndex];
117    [item setTarget:self];
118    NSValue* modelObject = [NSValue valueWithPointer:model];
119    [item setRepresentedObject:modelObject];  // Retains |modelObject|.
120    ui::AcceleratorCocoa accelerator;
121    if (model->GetAcceleratorAt(modelIndex, &accelerator)) {
122      [item setKeyEquivalent:accelerator.characters()];
123      [item setKeyEquivalentModifierMask:accelerator.modifiers()];
124    }
125  }
126  [menu insertItem:item atIndex:index];
127}
128
129// Called before the menu is to be displayed to update the state (enabled,
130// radio, etc) of each item in the menu. Also will update the title if
131// the item is marked as "dynamic".
132- (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)item {
133  SEL action = [item action];
134  if (action != @selector(itemSelected:))
135    return NO;
136
137  NSInteger modelIndex = [item tag];
138  ui::MenuModel* model =
139      static_cast<ui::MenuModel*>(
140          [[(id)item representedObject] pointerValue]);
141  DCHECK(model);
142  if (model) {
143    BOOL checked = model->IsItemCheckedAt(modelIndex);
144    DCHECK([(id)item isKindOfClass:[NSMenuItem class]]);
145    [(id)item setState:(checked ? NSOnState : NSOffState)];
146    [(id)item setHidden:(!model->IsVisibleAt(modelIndex))];
147    if (model->IsItemDynamicAt(modelIndex)) {
148      // Update the label and the icon.
149      NSString* label =
150          l10n_util::FixUpWindowsStyleLabel(model->GetLabelAt(modelIndex));
151      [(id)item setTitle:label];
152      SkBitmap skiaIcon;
153      NSImage* icon = nil;
154      if (model->GetIconAt(modelIndex, &skiaIcon) && !skiaIcon.isNull())
155        icon = gfx::SkBitmapToNSImage(skiaIcon);
156      [(id)item setImage:icon];
157    }
158    return model->IsEnabledAt(modelIndex);
159  }
160  return NO;
161}
162
163// Called when the user chooses a particular menu item. |sender| is the menu
164// item chosen.
165- (void)itemSelected:(id)sender {
166  NSInteger modelIndex = [sender tag];
167  ui::MenuModel* model =
168      static_cast<ui::MenuModel*>(
169          [[sender representedObject] pointerValue]);
170  DCHECK(model);
171  if (model)
172    model->ActivatedAt(modelIndex);
173}
174
175- (NSMenu*)menu {
176  if (!menu_ && model_) {
177    menu_.reset([[self menuFromModel:model_] retain]);
178    [menu_ setDelegate:self];
179    // If this is to be used with a NSPopUpButtonCell, add an item at the 0th
180    // position that's empty. Doing it after the menu has been constructed won't
181    // complicate creation logic, and since the tags are model indexes, they
182    // are unaffected by the extra item.
183    if (useWithPopUpButtonCell_) {
184      scoped_nsobject<NSMenuItem> blankItem(
185          [[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""]);
186      [menu_ insertItem:blankItem atIndex:0];
187    }
188  }
189  return menu_.get();
190}
191
192- (void)menuDidClose:(NSMenu*)menu {
193  model_->MenuClosed();
194}
195
196@end
197