• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright (c) 2011 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/wrench_menu/wrench_menu_controller.h"
6
7#include "base/sys_string_conversions.h"
8#include "chrome/app/chrome_command_ids.h"
9#include "chrome/browser/metrics/user_metrics.h"
10#include "chrome/browser/ui/browser.h"
11#include "chrome/browser/ui/browser_window.h"
12#import "chrome/browser/ui/cocoa/toolbar/toolbar_controller.h"
13#import "chrome/browser/ui/cocoa/wrench_menu/menu_tracked_root_view.h"
14#include "chrome/browser/ui/toolbar/wrench_menu_model.h"
15#include "content/common/notification_observer.h"
16#include "content/common/notification_service.h"
17#include "content/common/notification_source.h"
18#include "content/common/notification_type.h"
19#include "grit/chromium_strings.h"
20#include "grit/generated_resources.h"
21#include "ui/base/l10n/l10n_util.h"
22#include "ui/base/models/menu_model.h"
23
24@interface WrenchMenuController (Private)
25- (void)adjustPositioning;
26- (void)performCommandDispatch:(NSNumber*)tag;
27- (NSButton*)zoomDisplay;
28@end
29
30namespace WrenchMenuControllerInternal {
31
32class ZoomLevelObserver : public NotificationObserver {
33 public:
34  explicit ZoomLevelObserver(WrenchMenuController* controller)
35      : controller_(controller) {
36    registrar_.Add(this, NotificationType::ZOOM_LEVEL_CHANGED,
37                   NotificationService::AllSources());
38  }
39
40  void Observe(NotificationType type,
41               const NotificationSource& source,
42               const NotificationDetails& details) {
43    DCHECK_EQ(type.value, NotificationType::ZOOM_LEVEL_CHANGED);
44    WrenchMenuModel* wrenchMenuModel = [controller_ wrenchMenuModel];
45    wrenchMenuModel->UpdateZoomControls();
46    const string16 level =
47        wrenchMenuModel->GetLabelForCommandId(IDC_ZOOM_PERCENT_DISPLAY);
48    [[controller_ zoomDisplay] setTitle:SysUTF16ToNSString(level)];
49  }
50
51 private:
52  NotificationRegistrar registrar_;
53  WrenchMenuController* controller_;  // Weak; owns this.
54};
55
56}  // namespace WrenchMenuControllerInternal
57
58@implementation WrenchMenuController
59
60- (id)init {
61  if ((self = [super init])) {
62    observer_.reset(new WrenchMenuControllerInternal::ZoomLevelObserver(self));
63  }
64  return self;
65}
66
67- (void)addItemToMenu:(NSMenu*)menu
68              atIndex:(NSInteger)index
69            fromModel:(ui::MenuModel*)model
70           modelIndex:(int)modelIndex {
71  // Non-button item types should be built as normal items.
72  ui::MenuModel::ItemType type = model->GetTypeAt(modelIndex);
73  if (type != ui::MenuModel::TYPE_BUTTON_ITEM) {
74    [super addItemToMenu:menu
75                 atIndex:index
76               fromModel:model
77              modelIndex:modelIndex];
78    return;
79  }
80
81  // Handle the special-cased menu items.
82  int command_id = model->GetCommandIdAt(modelIndex);
83  scoped_nsobject<NSMenuItem> customItem(
84      [[NSMenuItem alloc] initWithTitle:@""
85                                 action:nil
86                          keyEquivalent:@""]);
87  switch (command_id) {
88    case IDC_EDIT_MENU:
89      DCHECK(editItem_);
90      [customItem setView:editItem_];
91      [editItem_ setMenuItem:customItem];
92      break;
93    case IDC_ZOOM_MENU:
94      DCHECK(zoomItem_);
95      [customItem setView:zoomItem_];
96      [zoomItem_ setMenuItem:customItem];
97      break;
98    default:
99      NOTREACHED();
100      break;
101  }
102  [self adjustPositioning];
103  [menu insertItem:customItem.get() atIndex:index];
104}
105
106- (NSMenu*)menu {
107  NSMenu* menu = [super menu];
108  if (![menu delegate]) {
109    [menu setDelegate:self];
110  }
111  return menu;
112}
113
114- (void)menuWillOpen:(NSMenu*)menu {
115  NSString* title = base::SysUTF16ToNSString(
116      [self wrenchMenuModel]->GetLabelForCommandId(IDC_ZOOM_PERCENT_DISPLAY));
117  [[zoomItem_ viewWithTag:IDC_ZOOM_PERCENT_DISPLAY] setTitle:title];
118  UserMetrics::RecordAction(UserMetricsAction("ShowAppMenu"));
119
120  NSImage* icon = [self wrenchMenuModel]->browser()->window()->IsFullscreen() ?
121      [NSImage imageNamed:NSImageNameExitFullScreenTemplate] :
122          [NSImage imageNamed:NSImageNameEnterFullScreenTemplate];
123  [zoomFullScreen_ setImage:icon];
124}
125
126// Used to dispatch commands from the Wrench menu. The custom items within the
127// menu cannot be hooked up directly to First Responder because the window in
128// which the controls reside is not the BrowserWindowController, but a
129// NSCarbonMenuWindow; this screws up the typical |-commandDispatch:| system.
130- (IBAction)dispatchWrenchMenuCommand:(id)sender {
131  NSInteger tag = [sender tag];
132  if (sender == zoomPlus_ || sender == zoomMinus_) {
133    // Do a direct dispatch rather than scheduling on the outermost run loop,
134    // which would not get hit until after the menu had closed.
135    [self performCommandDispatch:[NSNumber numberWithInt:tag]];
136
137    // The zoom buttons should not close the menu if opened sticky.
138    if ([sender respondsToSelector:@selector(isTracking)] &&
139        [sender performSelector:@selector(isTracking)]) {
140      [menu_ cancelTracking];
141    }
142  } else {
143    // The custom views within the Wrench menu are abnormal and keep the menu
144    // open after a target-action.  Close the menu manually.
145    [menu_ cancelTracking];
146    [self dispatchCommandInternal:tag];
147  }
148}
149
150- (void)dispatchCommandInternal:(NSInteger)tag {
151  // Executing certain commands from the nested run loop of the menu can lead
152  // to wonky behavior (e.g. http://crbug.com/49716). To avoid this, schedule
153  // the dispatch on the outermost run loop.
154  [self performSelector:@selector(performCommandDispatch:)
155             withObject:[NSNumber numberWithInt:tag]
156             afterDelay:0.0];
157}
158
159// Used to perform the actual dispatch on the outermost runloop.
160- (void)performCommandDispatch:(NSNumber*)tag {
161  [self wrenchMenuModel]->ExecuteCommand([tag intValue]);
162}
163
164- (WrenchMenuModel*)wrenchMenuModel {
165  return static_cast<WrenchMenuModel*>(model_);
166}
167
168// Fit the localized strings into the Cut/Copy/Paste control, then resize the
169// whole menu item accordingly.
170- (void)adjustPositioning {
171  const CGFloat kButtonPadding = 12;
172  CGFloat delta = 0;
173
174  // Go through the three buttons from right-to-left, adjusting the size to fit
175  // the localized strings while keeping them all aligned on their horizontal
176  // edges.
177  const size_t kAdjustViewCount = 3;
178  NSButton* views[kAdjustViewCount] = { editPaste_, editCopy_, editCut_ };
179  for (size_t i = 0; i < kAdjustViewCount; ++i) {
180    NSButton* button = views[i];
181    CGFloat originalWidth = NSWidth([button frame]);
182
183    // Do not let |-sizeToFit| change the height of the button.
184    NSSize size = [button frame].size;
185    [button sizeToFit];
186    size.width = [button frame].size.width + kButtonPadding;
187    [button setFrameSize:size];
188
189    CGFloat newWidth = size.width;
190    delta += newWidth - originalWidth;
191
192    NSRect frame = [button frame];
193    frame.origin.x -= delta;
194    [button setFrame:frame];
195  }
196
197  // Resize the menu item by the total amound the buttons changed so that the
198  // spacing between the buttons and the title remains the same.
199  NSRect itemFrame = [editItem_ frame];
200  itemFrame.size.width += delta;
201  [editItem_ setFrame:itemFrame];
202
203  // Also resize the superview of the buttons, which is an NSView used to slide
204  // when the item title is too big and GTM resizes it.
205  NSRect parentFrame = [[editCut_ superview] frame];
206  parentFrame.size.width += delta;
207  parentFrame.origin.x -= delta;
208  [[editCut_ superview] setFrame:parentFrame];
209}
210
211- (NSButton*)zoomDisplay {
212  return zoomDisplay_;
213}
214
215@end  // @implementation WrenchMenuController
216