Home | History | Annotate | Download | only in cocoa
      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