OLD | NEW |
| (Empty) |
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/basictypes.h" | |
8 #include "base/mac/bundle_locations.h" | |
9 #include "base/scoped_observer.h" | |
10 #include "base/strings/string16.h" | |
11 #include "base/strings/sys_string_conversions.h" | |
12 #include "chrome/app/chrome_command_ids.h" | |
13 #import "chrome/browser/app_controller_mac.h" | |
14 #include "chrome/browser/profiles/profile.h" | |
15 #include "chrome/browser/ui/browser.h" | |
16 #include "chrome/browser/ui/browser_window.h" | |
17 #import "chrome/browser/ui/cocoa/accelerators_cocoa.h" | |
18 #import "chrome/browser/ui/cocoa/bookmarks/bookmark_menu_bridge.h" | |
19 #import "chrome/browser/ui/cocoa/bookmarks/bookmark_menu_cocoa_controller.h" | |
20 #import "chrome/browser/ui/cocoa/browser_window_controller.h" | |
21 #import "chrome/browser/ui/cocoa/encoding_menu_controller_delegate_mac.h" | |
22 #import "chrome/browser/ui/cocoa/extensions/browser_actions_container_view.h" | |
23 #import "chrome/browser/ui/cocoa/extensions/browser_actions_controller.h" | |
24 #import "chrome/browser/ui/cocoa/l10n_util.h" | |
25 #import "chrome/browser/ui/cocoa/toolbar/toolbar_controller.h" | |
26 #import "chrome/browser/ui/cocoa/wrench_menu/menu_tracked_root_view.h" | |
27 #import "chrome/browser/ui/cocoa/wrench_menu/recent_tabs_menu_model_delegate.h" | |
28 #include "chrome/browser/ui/toolbar/app_menu_model.h" | |
29 #include "chrome/browser/ui/toolbar/recent_tabs_sub_menu_model.h" | |
30 #include "chrome/browser/ui/toolbar/toolbar_actions_bar.h" | |
31 #include "chrome/browser/ui/toolbar/toolbar_actions_bar_observer.h" | |
32 #include "chrome/grit/generated_resources.h" | |
33 #include "components/ui/zoom/zoom_event_manager.h" | |
34 #include "content/public/browser/user_metrics.h" | |
35 #include "ui/base/l10n/l10n_util.h" | |
36 #include "ui/base/models/menu_model.h" | |
37 #include "ui/gfx/geometry/size.h" | |
38 | |
39 namespace { | |
40 // Padding amounts on the left/right of a custom menu item (like the browser | |
41 // actions overflow container). | |
42 const int kLeftPadding = 16; | |
43 const int kRightPadding = 10; | |
44 | |
45 // In *very* extreme cases, it's possible that there are so many overflowed | |
46 // actions, we won't be able to show them all. Cap the height so that the | |
47 // overflow won't make the menu larger than the height of the screen. | |
48 // Note: With this height, we can show 104 actions. Less than 0.0002% of our | |
49 // users will be affected. | |
50 const int kMaxOverflowContainerHeight = 416; | |
51 } | |
52 | |
53 namespace wrench_menu_controller { | |
54 const CGFloat kWrenchBubblePointOffsetY = 6; | |
55 } | |
56 | |
57 using base::UserMetricsAction; | |
58 | |
59 @interface WrenchMenuController (Private) | |
60 - (void)createModel; | |
61 - (void)adjustPositioning; | |
62 - (void)performCommandDispatch:(NSNumber*)tag; | |
63 - (NSButton*)zoomDisplay; | |
64 - (void)menu:(NSMenu*)menu willHighlightItem:(NSMenuItem*)item; | |
65 - (void)removeAllItems:(NSMenu*)menu; | |
66 - (NSMenu*)recentTabsSubmenu; | |
67 - (RecentTabsSubMenuModel*)recentTabsMenuModel; | |
68 - (int)maxWidthForMenuModel:(ui::MenuModel*)model | |
69 modelIndex:(int)modelIndex; | |
70 @end | |
71 | |
72 namespace WrenchMenuControllerInternal { | |
73 | |
74 // A C++ delegate that handles the accelerators in the wrench menu. | |
75 class AcceleratorDelegate : public ui::AcceleratorProvider { | |
76 public: | |
77 bool GetAcceleratorForCommandId(int command_id, | |
78 ui::Accelerator* out_accelerator) override { | |
79 AcceleratorsCocoa* keymap = AcceleratorsCocoa::GetInstance(); | |
80 const ui::Accelerator* accelerator = | |
81 keymap->GetAcceleratorForCommand(command_id); | |
82 if (!accelerator) | |
83 return false; | |
84 *out_accelerator = *accelerator; | |
85 return true; | |
86 } | |
87 }; | |
88 | |
89 class ZoomLevelObserver { | |
90 public: | |
91 ZoomLevelObserver(WrenchMenuController* controller, | |
92 ui_zoom::ZoomEventManager* manager) | |
93 : controller_(controller) { | |
94 subscription_ = manager->AddZoomLevelChangedCallback( | |
95 base::Bind(&ZoomLevelObserver::OnZoomLevelChanged, | |
96 base::Unretained(this))); | |
97 } | |
98 | |
99 ~ZoomLevelObserver() {} | |
100 | |
101 private: | |
102 void OnZoomLevelChanged(const content::HostZoomMap::ZoomLevelChange& change) { | |
103 AppMenuModel* appMenuModel = [controller_ appMenuModel]; | |
104 appMenuModel->UpdateZoomControls(); | |
105 const base::string16 level = | |
106 appMenuModel->GetLabelForCommandId(IDC_ZOOM_PERCENT_DISPLAY); | |
107 [[controller_ zoomDisplay] setTitle:SysUTF16ToNSString(level)]; | |
108 } | |
109 | |
110 scoped_ptr<content::HostZoomMap::Subscription> subscription_; | |
111 | |
112 WrenchMenuController* controller_; // Weak; owns this. | |
113 | |
114 DISALLOW_COPY_AND_ASSIGN(ZoomLevelObserver); | |
115 }; | |
116 | |
117 class ToolbarActionsBarObserverHelper : public ToolbarActionsBarObserver { | |
118 public: | |
119 ToolbarActionsBarObserverHelper(WrenchMenuController* controller, | |
120 ToolbarActionsBar* toolbar_actions_bar) | |
121 : controller_(controller), | |
122 scoped_observer_(this) { | |
123 scoped_observer_.Add(toolbar_actions_bar); | |
124 } | |
125 ~ToolbarActionsBarObserverHelper() override {} | |
126 | |
127 private: | |
128 // ToolbarActionsBarObserver: | |
129 void OnToolbarActionsBarDestroyed() override { | |
130 scoped_observer_.RemoveAll(); | |
131 } | |
132 void OnToolbarActionsBarDidStartResize() override { | |
133 [controller_ updateBrowserActionsSubmenu]; | |
134 } | |
135 | |
136 WrenchMenuController* controller_; | |
137 ScopedObserver<ToolbarActionsBar, ToolbarActionsBarObserver> scoped_observer_; | |
138 | |
139 DISALLOW_COPY_AND_ASSIGN(ToolbarActionsBarObserverHelper); | |
140 }; | |
141 | |
142 } // namespace WrenchMenuControllerInternal | |
143 | |
144 @implementation WrenchMenuController | |
145 | |
146 - (id)initWithBrowser:(Browser*)browser { | |
147 if ((self = [super init])) { | |
148 browser_ = browser; | |
149 acceleratorDelegate_.reset( | |
150 new WrenchMenuControllerInternal::AcceleratorDelegate()); | |
151 [self createModel]; | |
152 } | |
153 return self; | |
154 } | |
155 | |
156 - (void)dealloc { | |
157 [self browserWillBeDestroyed]; | |
158 [super dealloc]; | |
159 } | |
160 | |
161 - (void)browserWillBeDestroyed { | |
162 // This method indicates imminent destruction. Destroy owned objects that hold | |
163 // a weak Browser*, or pass this call onto reference counted objects. | |
164 recentTabsMenuModelDelegate_.reset(); | |
165 [self setModel:nullptr]; | |
166 appMenuModel_.reset(); | |
167 buttonViewController_.reset(); | |
168 | |
169 // The observers should most likely already be destroyed (since they're reset | |
170 // in -menuDidClose:), but sometimes shutdown can be funny, so make sure to | |
171 // not leave any dangling observers. | |
172 zoom_level_observer_.reset(); | |
173 toolbar_actions_bar_observer_.reset(); | |
174 | |
175 [browserActionsController_ browserWillBeDestroyed]; | |
176 | |
177 browser_ = nullptr; | |
178 } | |
179 | |
180 - (void)addItemToMenu:(NSMenu*)menu | |
181 atIndex:(NSInteger)index | |
182 fromModel:(ui::MenuModel*)model { | |
183 // Non-button item types should be built as normal items, with the exception | |
184 // of the extensions overflow menu. | |
185 int command_id = model->GetCommandIdAt(index); | |
186 if (model->GetTypeAt(index) != ui::MenuModel::TYPE_BUTTON_ITEM && | |
187 command_id != IDC_EXTENSIONS_OVERFLOW_MENU) { | |
188 [super addItemToMenu:menu | |
189 atIndex:index | |
190 fromModel:model]; | |
191 return; | |
192 } | |
193 | |
194 // Handle the special-cased menu items. | |
195 base::scoped_nsobject<NSMenuItem> customItem( | |
196 [[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""]); | |
197 MenuTrackedRootView* view = nil; | |
198 switch (command_id) { | |
199 case IDC_EXTENSIONS_OVERFLOW_MENU: { | |
200 browserActionsMenuItem_ = customItem.get(); | |
201 view = [buttonViewController_ toolbarActionsOverflowItem]; | |
202 BrowserActionsContainerView* containerView = | |
203 [buttonViewController_ overflowActionsContainerView]; | |
204 | |
205 // The overflow browser actions container can't function properly without | |
206 // a main counterpart, so if the browser window hasn't initialized, abort. | |
207 // (This is fine because we re-populate the wrench menu each time before | |
208 // we show it.) | |
209 if (!browser_->window()) | |
210 break; | |
211 | |
212 BrowserActionsController* mainController = | |
213 [[[BrowserWindowController browserWindowControllerForWindow:browser_-> | |
214 window()->GetNativeWindow()] toolbarController] | |
215 browserActionsController]; | |
216 toolbar_actions_bar_observer_.reset( | |
217 new WrenchMenuControllerInternal::ToolbarActionsBarObserverHelper( | |
218 self, [mainController toolbarActionsBar])); | |
219 browserActionsController_.reset( | |
220 [[BrowserActionsController alloc] | |
221 initWithBrowser:browser_ | |
222 containerView:containerView | |
223 mainController:mainController]); | |
224 break; | |
225 } | |
226 case IDC_EDIT_MENU: | |
227 view = [buttonViewController_ editItem]; | |
228 break; | |
229 case IDC_ZOOM_MENU: | |
230 view = [buttonViewController_ zoomItem]; | |
231 break; | |
232 default: | |
233 NOTREACHED(); | |
234 break; | |
235 } | |
236 DCHECK(view); | |
237 [customItem setView:view]; | |
238 [view setMenuItem:customItem]; | |
239 [self adjustPositioning]; | |
240 [menu insertItem:customItem.get() atIndex:index]; | |
241 } | |
242 | |
243 - (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)item { | |
244 const BOOL enabled = [super validateUserInterfaceItem:item]; | |
245 | |
246 NSMenuItem* menuItem = (id)item; | |
247 ui::MenuModel* model = | |
248 static_cast<ui::MenuModel*>( | |
249 [[menuItem representedObject] pointerValue]); | |
250 | |
251 // The section headers in the recent tabs submenu should be bold and black if | |
252 // a font list is specified for the items (bold is already applied in the | |
253 // |MenuController| as the font list returned by |GetLabelFontListAt| is | |
254 // bold). | |
255 if (model && model == [self recentTabsMenuModel]) { | |
256 if (model->GetLabelFontListAt([item tag])) { | |
257 DCHECK([menuItem attributedTitle]); | |
258 base::scoped_nsobject<NSMutableAttributedString> title( | |
259 [[NSMutableAttributedString alloc] | |
260 initWithAttributedString:[menuItem attributedTitle]]); | |
261 [title addAttribute:NSForegroundColorAttributeName | |
262 value:[NSColor blackColor] | |
263 range:NSMakeRange(0, [title length])]; | |
264 [menuItem setAttributedTitle:title.get()]; | |
265 } else { | |
266 // Not a section header. Add a tooltip with the title and the URL. | |
267 std::string url; | |
268 base::string16 title; | |
269 if ([self recentTabsMenuModel]->GetURLAndTitleForItemAtIndex( | |
270 [item tag], &url, &title)) { | |
271 [menuItem setToolTip: | |
272 cocoa_l10n_util::TooltipForURLAndTitle( | |
273 base::SysUTF8ToNSString(url), base::SysUTF16ToNSString(title))]; | |
274 } | |
275 } | |
276 } | |
277 | |
278 return enabled; | |
279 } | |
280 | |
281 - (NSMenu*)bookmarkSubMenu { | |
282 NSString* title = l10n_util::GetNSStringWithFixup(IDS_BOOKMARKS_MENU); | |
283 return [[[self menu] itemWithTitle:title] submenu]; | |
284 } | |
285 | |
286 - (void)updateBookmarkSubMenu { | |
287 NSMenu* bookmarkMenu = [self bookmarkSubMenu]; | |
288 DCHECK(bookmarkMenu); | |
289 | |
290 bookmarkMenuBridge_.reset(new BookmarkMenuBridge( | |
291 [self appMenuModel]->browser()->profile(), bookmarkMenu)); | |
292 } | |
293 | |
294 - (void)updateBrowserActionsSubmenu { | |
295 MenuTrackedRootView* view = | |
296 [buttonViewController_ toolbarActionsOverflowItem]; | |
297 BrowserActionsContainerView* containerView = | |
298 [buttonViewController_ overflowActionsContainerView]; | |
299 | |
300 // Find the preferred container size for the menu width. | |
301 int menuWidth = [[self menu] size].width; | |
302 int maxContainerWidth = menuWidth - kLeftPadding - kRightPadding; | |
303 // Don't let the menu change sizes on us. (We lift this restriction every time | |
304 // the menu updates, so if something changes, this won't leave us with an | |
305 // awkward size.) | |
306 [[self menu] setMinimumWidth:menuWidth]; | |
307 gfx::Size preferredContainerSize = | |
308 [browserActionsController_ sizeForOverflowWidth:maxContainerWidth]; | |
309 | |
310 // Set the origins and preferred size for the container. | |
311 // View hierarchy is as follows (from parent > child): | |
312 // |view| > |anonymous view| > containerView. We have to set the origin | |
313 // and size of each for it display properly. | |
314 // The parent views each have a size of the full width of the menu, so we can | |
315 // properly position the container. | |
316 NSSize parentSize = NSMakeSize(menuWidth, | |
317 std::min(preferredContainerSize.height(), | |
318 kMaxOverflowContainerHeight)); | |
319 [view setFrameSize:parentSize]; | |
320 [[containerView superview] setFrameSize:parentSize]; | |
321 | |
322 // The container view gets its preferred size. | |
323 [containerView setFrameSize:NSMakeSize(preferredContainerSize.width(), | |
324 preferredContainerSize.height())]; | |
325 [browserActionsController_ update]; | |
326 | |
327 [view setFrameOrigin:NSZeroPoint]; | |
328 [[containerView superview] setFrameOrigin:NSZeroPoint]; | |
329 [containerView setFrameOrigin:NSMakePoint(kLeftPadding, 0)]; | |
330 } | |
331 | |
332 - (void)menuWillOpen:(NSMenu*)menu { | |
333 [super menuWillOpen:menu]; | |
334 | |
335 zoom_level_observer_.reset( | |
336 new WrenchMenuControllerInternal::ZoomLevelObserver( | |
337 self, | |
338 ui_zoom::ZoomEventManager::GetForBrowserContext( | |
339 browser_->profile()))); | |
340 NSString* title = base::SysUTF16ToNSString( | |
341 [self appMenuModel]->GetLabelForCommandId(IDC_ZOOM_PERCENT_DISPLAY)); | |
342 [[[buttonViewController_ zoomItem] viewWithTag:IDC_ZOOM_PERCENT_DISPLAY] | |
343 setTitle:title]; | |
344 content::RecordAction(UserMetricsAction("ShowAppMenu")); | |
345 | |
346 NSImage* icon = [self appMenuModel]->browser()->window()->IsFullscreen() | |
347 ? [NSImage imageNamed:NSImageNameExitFullScreenTemplate] | |
348 : [NSImage imageNamed:NSImageNameEnterFullScreenTemplate]; | |
349 [[buttonViewController_ zoomFullScreen] setImage:icon]; | |
350 } | |
351 | |
352 - (void)menuDidClose:(NSMenu*)menu { | |
353 [super menuDidClose:menu]; | |
354 // We don't need to observe changes to zoom or toolbar size when the menu is | |
355 // closed, since we instantiate it with the proper value and recreate the menu | |
356 // on each show. (We do this in -menuNeedsUpdate:, which is called when the | |
357 // menu is about to be displayed at the start of a tracking session.) | |
358 zoom_level_observer_.reset(); | |
359 toolbar_actions_bar_observer_.reset(); | |
360 } | |
361 | |
362 - (void)menuNeedsUpdate:(NSMenu*)menu { | |
363 // First empty out the menu and create a new model. | |
364 [self removeAllItems:menu]; | |
365 [self createModel]; | |
366 [menu setMinimumWidth:0]; | |
367 | |
368 // Create a new menu, which cannot be swapped because the tracking is about to | |
369 // start, so simply copy the items. | |
370 NSMenu* newMenu = [self menuFromModel:model_]; | |
371 NSArray* itemArray = [newMenu itemArray]; | |
372 [self removeAllItems:newMenu]; | |
373 for (NSMenuItem* item in itemArray) { | |
374 [menu addItem:item]; | |
375 } | |
376 | |
377 [self updateRecentTabsSubmenu]; | |
378 [self updateBookmarkSubMenu]; | |
379 [self updateBrowserActionsSubmenu]; | |
380 } | |
381 | |
382 // Used to dispatch commands from the Wrench menu. The custom items within the | |
383 // menu cannot be hooked up directly to First Responder because the window in | |
384 // which the controls reside is not the BrowserWindowController, but a | |
385 // NSCarbonMenuWindow; this screws up the typical |-commandDispatch:| system. | |
386 - (IBAction)dispatchWrenchMenuCommand:(id)sender { | |
387 NSInteger tag = [sender tag]; | |
388 if (sender == [buttonViewController_ zoomPlus] || | |
389 sender == [buttonViewController_ zoomMinus]) { | |
390 // Do a direct dispatch rather than scheduling on the outermost run loop, | |
391 // which would not get hit until after the menu had closed. | |
392 [self performCommandDispatch:[NSNumber numberWithInt:tag]]; | |
393 | |
394 // The zoom buttons should not close the menu if opened sticky. | |
395 if ([sender respondsToSelector:@selector(isTracking)] && | |
396 [sender performSelector:@selector(isTracking)]) { | |
397 [menu_ cancelTracking]; | |
398 } | |
399 } else { | |
400 // The custom views within the Wrench menu are abnormal and keep the menu | |
401 // open after a target-action. Close the menu manually. | |
402 [menu_ cancelTracking]; | |
403 | |
404 // Executing certain commands from the nested run loop of the menu can lead | |
405 // to wonky behavior (e.g. http://crbug.com/49716). To avoid this, schedule | |
406 // the dispatch on the outermost run loop. | |
407 [self performSelector:@selector(performCommandDispatch:) | |
408 withObject:[NSNumber numberWithInt:tag] | |
409 afterDelay:0.0]; | |
410 } | |
411 } | |
412 | |
413 // Used to perform the actual dispatch on the outermost runloop. | |
414 - (void)performCommandDispatch:(NSNumber*)tag { | |
415 [self appMenuModel]->ExecuteCommand([tag intValue], 0); | |
416 } | |
417 | |
418 - (AppMenuModel*)appMenuModel { | |
419 // Don't use |appMenuModel_| so that a test can override the generic one. | |
420 return static_cast<AppMenuModel*>(model_); | |
421 } | |
422 | |
423 - (void)updateRecentTabsSubmenu { | |
424 ui::MenuModel* model = [self recentTabsMenuModel]; | |
425 if (model) { | |
426 recentTabsMenuModelDelegate_.reset( | |
427 new RecentTabsMenuModelDelegate(model, [self recentTabsSubmenu])); | |
428 } | |
429 } | |
430 | |
431 - (BrowserActionsController*)browserActionsController { | |
432 return browserActionsController_.get(); | |
433 } | |
434 | |
435 - (void)createModel { | |
436 DCHECK(browser_); | |
437 recentTabsMenuModelDelegate_.reset(); | |
438 appMenuModel_.reset(new AppMenuModel(acceleratorDelegate_.get(), browser_)); | |
439 [self setModel:appMenuModel_.get()]; | |
440 | |
441 buttonViewController_.reset( | |
442 [[WrenchMenuButtonViewController alloc] initWithController:self]); | |
443 [buttonViewController_ view]; | |
444 | |
445 // See comment in containerSuperviewFrameChanged:. | |
446 NSView* containerSuperview = | |
447 [[buttonViewController_ overflowActionsContainerView] superview]; | |
448 [containerSuperview setPostsFrameChangedNotifications:YES]; | |
449 } | |
450 | |
451 // Fit the localized strings into the Cut/Copy/Paste control, then resize the | |
452 // whole menu item accordingly. | |
453 - (void)adjustPositioning { | |
454 const CGFloat kButtonPadding = 12; | |
455 CGFloat delta = 0; | |
456 | |
457 // Go through the three buttons from right-to-left, adjusting the size to fit | |
458 // the localized strings while keeping them all aligned on their horizontal | |
459 // edges. | |
460 NSButton* views[] = { | |
461 [buttonViewController_ editPaste], | |
462 [buttonViewController_ editCopy], | |
463 [buttonViewController_ editCut] | |
464 }; | |
465 for (size_t i = 0; i < arraysize(views); ++i) { | |
466 NSButton* button = views[i]; | |
467 CGFloat originalWidth = NSWidth([button frame]); | |
468 | |
469 // Do not let |-sizeToFit| change the height of the button. | |
470 NSSize size = [button frame].size; | |
471 [button sizeToFit]; | |
472 size.width = [button frame].size.width + kButtonPadding; | |
473 [button setFrameSize:size]; | |
474 | |
475 CGFloat newWidth = size.width; | |
476 delta += newWidth - originalWidth; | |
477 | |
478 NSRect frame = [button frame]; | |
479 frame.origin.x -= delta; | |
480 [button setFrame:frame]; | |
481 } | |
482 | |
483 // Resize the menu item by the total amound the buttons changed so that the | |
484 // spacing between the buttons and the title remains the same. | |
485 NSRect itemFrame = [[buttonViewController_ editItem] frame]; | |
486 itemFrame.size.width += delta; | |
487 [[buttonViewController_ editItem] setFrame:itemFrame]; | |
488 | |
489 // Also resize the superview of the buttons, which is an NSView used to slide | |
490 // when the item title is too big and GTM resizes it. | |
491 NSRect parentFrame = [[[buttonViewController_ editCut] superview] frame]; | |
492 parentFrame.size.width += delta; | |
493 parentFrame.origin.x -= delta; | |
494 [[[buttonViewController_ editCut] superview] setFrame:parentFrame]; | |
495 } | |
496 | |
497 - (NSButton*)zoomDisplay { | |
498 return [buttonViewController_ zoomDisplay]; | |
499 } | |
500 | |
501 - (void)menu:(NSMenu*)menu willHighlightItem:(NSMenuItem*)item { | |
502 if (browserActionsController_.get()) { | |
503 [browserActionsController_ setFocusedInOverflow: | |
504 (item == browserActionsMenuItem_)]; | |
505 } | |
506 } | |
507 | |
508 // -[NSMenu removeAllItems] is only available on 10.6+. | |
509 - (void)removeAllItems:(NSMenu*)menu { | |
510 while ([menu numberOfItems]) { | |
511 [menu removeItemAtIndex:0]; | |
512 } | |
513 } | |
514 | |
515 - (NSMenu*)recentTabsSubmenu { | |
516 NSString* title = l10n_util::GetNSStringWithFixup(IDS_RECENT_TABS_MENU); | |
517 return [[[self menu] itemWithTitle:title] submenu]; | |
518 } | |
519 | |
520 // The recent tabs menu model is recognized by the existence of either the | |
521 // kRecentlyClosedHeaderCommandId or the kDisabledRecentlyClosedHeaderCommandId. | |
522 - (RecentTabsSubMenuModel*)recentTabsMenuModel { | |
523 int index = 0; | |
524 // Start searching at the wrench menu model level, |model| will be updated | |
525 // only if the command we're looking for is found in one of the [sub]menus. | |
526 ui::MenuModel* model = [self appMenuModel]; | |
527 if (ui::MenuModel::GetModelAndIndexForCommandId( | |
528 RecentTabsSubMenuModel::kRecentlyClosedHeaderCommandId, &model, | |
529 &index)) { | |
530 return static_cast<RecentTabsSubMenuModel*>(model); | |
531 } | |
532 if (ui::MenuModel::GetModelAndIndexForCommandId( | |
533 RecentTabsSubMenuModel::kDisabledRecentlyClosedHeaderCommandId, | |
534 &model, &index)) { | |
535 return static_cast<RecentTabsSubMenuModel*>(model); | |
536 } | |
537 return NULL; | |
538 } | |
539 | |
540 // This overrdies the parent class to return a custom width for recent tabs | |
541 // menu. | |
542 - (int)maxWidthForMenuModel:(ui::MenuModel*)model | |
543 modelIndex:(int)modelIndex { | |
544 RecentTabsSubMenuModel* recentTabsMenuModel = [self recentTabsMenuModel]; | |
545 if (recentTabsMenuModel && recentTabsMenuModel == model) { | |
546 return recentTabsMenuModel->GetMaxWidthForItemAtIndex(modelIndex); | |
547 } | |
548 return -1; | |
549 } | |
550 | |
551 @end // @implementation WrenchMenuController | |
552 | |
553 //////////////////////////////////////////////////////////////////////////////// | |
554 | |
555 @interface WrenchMenuButtonViewController () | |
556 - (void)containerSuperviewFrameChanged:(NSNotification*)notification; | |
557 @end | |
558 | |
559 @implementation WrenchMenuButtonViewController | |
560 | |
561 @synthesize editItem = editItem_; | |
562 @synthesize editCut = editCut_; | |
563 @synthesize editCopy = editCopy_; | |
564 @synthesize editPaste = editPaste_; | |
565 @synthesize zoomItem = zoomItem_; | |
566 @synthesize zoomPlus = zoomPlus_; | |
567 @synthesize zoomDisplay = zoomDisplay_; | |
568 @synthesize zoomMinus = zoomMinus_; | |
569 @synthesize zoomFullScreen = zoomFullScreen_; | |
570 @synthesize toolbarActionsOverflowItem = toolbarActionsOverflowItem_; | |
571 @synthesize overflowActionsContainerView = overflowActionsContainerView_; | |
572 | |
573 - (id)initWithController:(WrenchMenuController*)controller { | |
574 if ((self = [super initWithNibName:@"WrenchMenu" | |
575 bundle:base::mac::FrameworkBundle()])) { | |
576 propertyReleaser_.Init(self, [WrenchMenuButtonViewController class]); | |
577 controller_ = controller; | |
578 [[NSNotificationCenter defaultCenter] | |
579 addObserver:self | |
580 selector:@selector(containerSuperviewFrameChanged:) | |
581 name:NSViewFrameDidChangeNotification | |
582 object:[overflowActionsContainerView_ superview]]; | |
583 } | |
584 return self; | |
585 } | |
586 | |
587 - (void)dealloc { | |
588 [[NSNotificationCenter defaultCenter] removeObserver:self]; | |
589 [super dealloc]; | |
590 } | |
591 | |
592 - (IBAction)dispatchWrenchMenuCommand:(id)sender { | |
593 [controller_ dispatchWrenchMenuCommand:sender]; | |
594 } | |
595 | |
596 - (void)containerSuperviewFrameChanged:(NSNotification*)notification { | |
597 // AppKit menus were probably never designed with a view like the browser | |
598 // actions container in mind, and, as a result, we come across a few oddities. | |
599 // One of these is that the container's superview will, on some versions of | |
600 // OSX, change frame position sometime after the the menu begins tracking | |
601 // (and thus, after all our ability to adjust it normally). Throw in the | |
602 // towel, and simply don't let the frame move from where it's supposed to be. | |
603 // TODO(devlin): Yet another Cocoa hack. It'd be good to find a workaround, | |
604 // but unlikely unless we replace the Cocoa menu implementation. | |
605 NSView* containerSuperview = [overflowActionsContainerView_ superview]; | |
606 if (NSMinX([containerSuperview frame]) != 0) | |
607 [containerSuperview setFrameOrigin:NSZeroPoint]; | |
608 } | |
609 | |
610 @end // @implementation WrenchMenuButtonViewController | |
OLD | NEW |