Roll src/third_party/WebKit 4526c29:30f3df0 (svn 190187:190189)
[chromium-blink-merge.git] / ui / base / cocoa / menu_controller.mm
blob9faa6071e44c4bc691b518976fb2f1d31400038d
1 // Copyright 2013 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.
5 #import "ui/base/cocoa/menu_controller.h"
7 #include "base/logging.h"
8 #include "base/strings/sys_string_conversions.h"
9 #include "ui/base/accelerators/accelerator.h"
10 #include "ui/base/accelerators/platform_accelerator_cocoa.h"
11 #include "ui/base/l10n/l10n_util_mac.h"
12 #include "ui/base/models/simple_menu_model.h"
13 #import "ui/events/event_utils.h"
14 #include "ui/gfx/font_list.h"
15 #include "ui/gfx/image/image.h"
16 #include "ui/gfx/text_elider.h"
18 @interface MenuController (Private)
19 - (void)addSeparatorToMenu:(NSMenu*)menu
20                    atIndex:(int)index;
21 @end
23 @implementation MenuController
25 @synthesize model = model_;
26 @synthesize useWithPopUpButtonCell = useWithPopUpButtonCell_;
28 + (base::string16)elideMenuTitle:(const base::string16&)title
29                          toWidth:(int)width {
30   NSFont* nsfont = [NSFont menuBarFontOfSize:0];  // 0 means "default"
31   return gfx::ElideText(title, gfx::FontList(gfx::Font(nsfont)), width,
32                         gfx::ELIDE_TAIL);
35 - (id)init {
36   self = [super init];
37   return self;
40 - (id)initWithModel:(ui::MenuModel*)model
41     useWithPopUpButtonCell:(BOOL)useWithCell {
42   if ((self = [super init])) {
43     model_ = model;
44     useWithPopUpButtonCell_ = useWithCell;
45     [self menu];
46   }
47   return self;
50 - (void)dealloc {
51   [menu_ setDelegate:nil];
53   // Close the menu if it is still open. This could happen if a tab gets closed
54   // while its context menu is still open.
55   [self cancel];
57   model_ = NULL;
58   [super dealloc];
61 - (void)cancel {
62   if (isMenuOpen_) {
63     [menu_ cancelTracking];
64     model_->MenuClosed();
65     isMenuOpen_ = NO;
66   }
69 // Creates a NSMenu from the given model. If the model has submenus, this can
70 // be invoked recursively.
71 - (NSMenu*)menuFromModel:(ui::MenuModel*)model {
72   NSMenu* menu = [[[NSMenu alloc] initWithTitle:@""] autorelease];
74   const int count = model->GetItemCount();
75   for (int index = 0; index < count; index++) {
76     if (model->GetTypeAt(index) == ui::MenuModel::TYPE_SEPARATOR)
77       [self addSeparatorToMenu:menu atIndex:index];
78     else
79       [self addItemToMenu:menu atIndex:index fromModel:model];
80   }
82   return menu;
85 - (int)maxWidthForMenuModel:(ui::MenuModel*)model
86                  modelIndex:(int)modelIndex {
87   return -1;
90 // Adds a separator item at the given index. As the separator doesn't need
91 // anything from the model, this method doesn't need the model index as the
92 // other method below does.
93 - (void)addSeparatorToMenu:(NSMenu*)menu
94                    atIndex:(int)index {
95   NSMenuItem* separator = [NSMenuItem separatorItem];
96   [menu insertItem:separator atIndex:index];
99 // Adds an item or a hierarchical menu to the item at the |index|,
100 // associated with the entry in the model identified by |modelIndex|.
101 - (void)addItemToMenu:(NSMenu*)menu
102               atIndex:(NSInteger)index
103             fromModel:(ui::MenuModel*)model {
104   base::string16 label16 = model->GetLabelAt(index);
105   int maxWidth = [self maxWidthForMenuModel:model modelIndex:index];
106   if (maxWidth != -1)
107     label16 = [MenuController elideMenuTitle:label16 toWidth:maxWidth];
109   NSString* label = l10n_util::FixUpWindowsStyleLabel(label16);
110   base::scoped_nsobject<NSMenuItem> item(
111       [[NSMenuItem alloc] initWithTitle:label
112                                  action:@selector(itemSelected:)
113                           keyEquivalent:@""]);
115   // If the menu item has an icon, set it.
116   gfx::Image icon;
117   if (model->GetIconAt(index, &icon) && !icon.IsEmpty())
118     [item setImage:icon.ToNSImage()];
120   ui::MenuModel::ItemType type = model->GetTypeAt(index);
121   if (type == ui::MenuModel::TYPE_SUBMENU) {
122     // Recursively build a submenu from the sub-model at this index.
123     [item setTarget:nil];
124     [item setAction:nil];
125     ui::MenuModel* submenuModel = model->GetSubmenuModelAt(index);
126     NSMenu* submenu =
127         [self menuFromModel:(ui::SimpleMenuModel*)submenuModel];
128     [item setSubmenu:submenu];
129   } else {
130     // The MenuModel works on indexes so we can't just set the command id as the
131     // tag like we do in other menus. Also set the represented object to be
132     // the model so hierarchical menus check the correct index in the correct
133     // model. Setting the target to |self| allows this class to participate
134     // in validation of the menu items.
135     [item setTag:index];
136     [item setTarget:self];
137     NSValue* modelObject = [NSValue valueWithPointer:model];
138     [item setRepresentedObject:modelObject];  // Retains |modelObject|.
139     ui::Accelerator accelerator;
140     if (model->GetAcceleratorAt(index, &accelerator)) {
141       const ui::PlatformAcceleratorCocoa* platformAccelerator =
142           static_cast<const ui::PlatformAcceleratorCocoa*>(
143               accelerator.platform_accelerator());
144       if (platformAccelerator) {
145         [item setKeyEquivalent:platformAccelerator->characters()];
146         [item setKeyEquivalentModifierMask:
147             platformAccelerator->modifier_mask()];
148       }
149     }
150   }
151   [menu insertItem:item atIndex:index];
154 // Called before the menu is to be displayed to update the state (enabled,
155 // radio, etc) of each item in the menu. Also will update the title if
156 // the item is marked as "dynamic".
157 - (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)item {
158   SEL action = [item action];
159   if (action != @selector(itemSelected:))
160     return NO;
162   NSInteger modelIndex = [item tag];
163   ui::MenuModel* model =
164       static_cast<ui::MenuModel*>(
165           [[(id)item representedObject] pointerValue]);
166   DCHECK(model);
167   if (model) {
168     BOOL checked = model->IsItemCheckedAt(modelIndex);
169     DCHECK([(id)item isKindOfClass:[NSMenuItem class]]);
170     [(id)item setState:(checked ? NSOnState : NSOffState)];
171     [(id)item setHidden:(!model->IsVisibleAt(modelIndex))];
172     if (model->IsItemDynamicAt(modelIndex)) {
173       // Update the label and the icon.
174       NSString* label =
175           l10n_util::FixUpWindowsStyleLabel(model->GetLabelAt(modelIndex));
176       [(id)item setTitle:label];
178       gfx::Image icon;
179       model->GetIconAt(modelIndex, &icon);
180       [(id)item setImage:icon.IsEmpty() ? nil : icon.ToNSImage()];
181     }
182     const gfx::FontList* font_list = model->GetLabelFontListAt(modelIndex);
183     if (font_list) {
184       NSDictionary *attributes =
185           [NSDictionary dictionaryWithObject:font_list->GetPrimaryFont().
186                                              GetNativeFont()
187                                       forKey:NSFontAttributeName];
188       base::scoped_nsobject<NSAttributedString> title(
189           [[NSAttributedString alloc] initWithString:[(id)item title]
190                                           attributes:attributes]);
191       [(id)item setAttributedTitle:title.get()];
192     }
193     return model->IsEnabledAt(modelIndex);
194   }
195   return NO;
198 // Called when the user chooses a particular menu item. |sender| is the menu
199 // item chosen.
200 - (void)itemSelected:(id)sender {
201   NSInteger modelIndex = [sender tag];
202   ui::MenuModel* model =
203       static_cast<ui::MenuModel*>(
204           [[sender representedObject] pointerValue]);
205   DCHECK(model);
206   if (model) {
207     int event_flags = ui::EventFlagsFromNative([NSApp currentEvent]);
208     model->ActivatedAt(modelIndex, event_flags);
209   }
212 - (NSMenu*)menu {
213   if (!menu_ && model_) {
214     menu_.reset([[self menuFromModel:model_] retain]);
215     [menu_ setDelegate:self];
216     // If this is to be used with a NSPopUpButtonCell, add an item at the 0th
217     // position that's empty. Doing it after the menu has been constructed won't
218     // complicate creation logic, and since the tags are model indexes, they
219     // are unaffected by the extra item.
220     if (useWithPopUpButtonCell_) {
221       base::scoped_nsobject<NSMenuItem> blankItem(
222           [[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""]);
223       [menu_ insertItem:blankItem atIndex:0];
224     }
225   }
226   return menu_.get();
229 - (BOOL)isMenuOpen {
230   return isMenuOpen_;
233 - (void)menuWillOpen:(NSMenu*)menu {
234   isMenuOpen_ = YES;
235   model_->MenuWillShow();
238 - (void)menuDidClose:(NSMenu*)menu {
239   if (isMenuOpen_) {
240     model_->MenuClosed();
241     isMenuOpen_ = NO;
242   }
245 @end