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