| 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 |