summaryrefslogtreecommitdiffstats
path: root/chrome/browser/cocoa/menu_controller.mm
blob: d095a2eb2577e7df180c5612794b2c3dcfd0852f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
// Copyright (c) 2010 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#import "chrome/browser/cocoa/menu_controller.h"

#include "app/l10n_util_mac.h"
#include "app/menus/simple_menu_model.h"
#include "base/logging.h"
#include "base/sys_string_conversions.h"

@interface MenuController(Private)
- (NSMenu*)menuFromModel:(menus::MenuModel*)model;
- (void)addSeparatorToMenu:(NSMenu*)menu
                   atIndex:(int)index;
- (void)addItemToMenu:(NSMenu*)menu
              atIndex:(int)index
            fromModel:(menus::MenuModel*)model
           modelIndex:(int)modelIndex;
@end

@implementation MenuController

- (id)initWithModel:(menus::MenuModel*)model
    useWithPopUpButtonCell:(BOOL)useWithCell {
  if ((self = [super init])) {
    menu_.reset([[self menuFromModel:model] retain]);
    // If this is to be used with a NSPopUpButtonCell, add an item at the 0th
    // position that's empty. Doing it after the menu has been constructed won't
    // complicate creation logic, and since the tags are model indexes, they
    // are unaffected by the extra item.
    if (useWithCell) {
      scoped_nsobject<NSMenuItem> blankItem(
          [[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""]);
      [menu_ insertItem:blankItem atIndex:0];
    }
  }
  return self;
}

// Creates a NSMenu from the given model. If the model has submenus, this can
// be invoked recursively.
- (NSMenu*)menuFromModel:(menus::MenuModel*)model {
  NSMenu* menu = [[[NSMenu alloc] initWithTitle:@""] autorelease];

  // The indices may not always start at zero (the windows system menu is one
  // example where this is used) so just make sure we can handle it.
  // SimpleMenuModel currently always starts at 0.
  int firstItemIndex = model->GetFirstItemIndex(menu);
  DCHECK(firstItemIndex == 0);
  const int count = model->GetItemCount();
  for (int index = firstItemIndex; index < firstItemIndex + count; index++) {
    int modelIndex = index - firstItemIndex;
    if (model->GetTypeAt(modelIndex) == menus::MenuModel::TYPE_SEPARATOR) {
      [self addSeparatorToMenu:menu atIndex:index];
    } else {
      [self addItemToMenu:menu atIndex:index fromModel:model
          modelIndex:modelIndex];
    }
  }

  return menu;
}

// Adds a separator item at the given index. As the separator doesn't need
// anything from the model, this method doesn't need the model index as the
// other method below does.
- (void)addSeparatorToMenu:(NSMenu*)menu
                   atIndex:(int)index {
  NSMenuItem* separator = [NSMenuItem separatorItem];
  [menu insertItem:separator atIndex:index];
}

// Adds an item or a hierarchical menu to the item at the |index|,
// associated with the entry in the model indentifed by |modelIndex|.
- (void)addItemToMenu:(NSMenu*)menu
              atIndex:(int)index
            fromModel:(menus::MenuModel*)model
           modelIndex:(int)modelIndex {
  NSString* label =
      l10n_util::FixUpWindowsStyleLabel(model->GetLabelAt(modelIndex));
  scoped_nsobject<NSMenuItem> item(
      [[NSMenuItem alloc] initWithTitle:label
                                 action:@selector(itemSelected:)
                          keyEquivalent:@""]);
  menus::MenuModel::ItemType type = model->GetTypeAt(modelIndex);
  if (type == menus::MenuModel::TYPE_SUBMENU) {
    // Recursively build a submenu from the sub-model at this index.
    [item setTarget:nil];
    [item setAction:nil];
    menus::MenuModel* submenuModel = model->GetSubmenuModelAt(modelIndex);
    NSMenu* submenu =
        [self menuFromModel:(menus::SimpleMenuModel*)submenuModel];
    [item setSubmenu:submenu];
  } else {
    // The MenuModel works on indexes so we can't just set the command id as the
    // tag like we do in other menus. Also set the represented object to be
    // the model so hierarchical menus check the correct index in the correct
    // model. Setting the target to |self| allows this class to participate
    // in validation of the menu items.
    [item setTag:modelIndex];
    [item setTarget:self];
    NSValue* modelObject = [NSValue valueWithPointer:model];
    [item setRepresentedObject:modelObject];  // Retains |modelObject|.
  }
  [menu insertItem:item atIndex:index];
}

// Called before the menu is to be displayed to update the state (enabled,
// radio, etc) of each item in the menu. Also will update the title if
// the item is marked as "dynamic".
- (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)item {
  SEL action = [item action];
  if (action != @selector(itemSelected:))
    return NO;

  NSInteger modelIndex = [item tag];
  menus::MenuModel* model =
      static_cast<menus::MenuModel*>(
          [[(id)item representedObject] pointerValue]);
  DCHECK(model);
  if (model) {
    BOOL checked = model->IsItemCheckedAt(modelIndex);
    DCHECK([(id)item isKindOfClass:[NSMenuItem class]]);
    [(id)item setState:(checked ? NSOnState : NSOffState)];
    if (model->IsLabelDynamicAt(modelIndex)) {
      NSString* label =
          l10n_util::FixUpWindowsStyleLabel(model->GetLabelAt(modelIndex));
      [(id)item setTitle:label];
    }
    return model->IsEnabledAt(modelIndex);
  }
  return NO;
}

// Called when the user chooses a particular menu item. |sender| is the menu
// item chosen.
- (void)itemSelected:(id)sender {
  NSInteger modelIndex = [sender tag];
  menus::MenuModel* model =
      static_cast<menus::MenuModel*>(
          [[sender representedObject] pointerValue]);
  DCHECK(model);
  if (model);
    model->ActivatedAt(modelIndex);
}

- (NSMenu*)menu {
  return menu_.get();
}

@end