toolbar_controller.mm revision 72a454cd3513ac24fbdd0e0cb9ad70b86a99b801
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/toolbar/toolbar_controller.h" 6 7#include <algorithm> 8 9#include "ui/base/l10n/l10n_util.h" 10#include "app/mac/nsimage_cache.h" 11#include "base/mac/mac_util.h" 12#include "base/singleton.h" 13#include "base/sys_string_conversions.h" 14#include "chrome/app/chrome_command_ids.h" 15#include "chrome/browser/autocomplete/autocomplete.h" 16#include "chrome/browser/autocomplete/autocomplete_classifier.h" 17#include "chrome/browser/autocomplete/autocomplete_edit_view.h" 18#include "chrome/browser/autocomplete/autocomplete_match.h" 19#include "chrome/browser/background_page_tracker.h" 20#include "chrome/browser/net/url_fixer_upper.h" 21#include "chrome/browser/prefs/pref_service.h" 22#include "chrome/browser/profiles/profile.h" 23#include "chrome/browser/search_engines/template_url_model.h" 24#include "chrome/browser/tab_contents/tab_contents.h" 25#include "chrome/browser/themes/browser_theme_provider.h" 26#include "chrome/browser/upgrade_detector.h" 27#include "chrome/browser/ui/browser.h" 28#include "chrome/browser/ui/browser_window.h" 29#import "chrome/browser/ui/cocoa/accelerators_cocoa.h" 30#import "chrome/browser/ui/cocoa/background_gradient_view.h" 31#import "chrome/browser/ui/cocoa/encoding_menu_controller_delegate_mac.h" 32#import "chrome/browser/ui/cocoa/extensions/browser_action_button.h" 33#import "chrome/browser/ui/cocoa/extensions/browser_actions_container_view.h" 34#import "chrome/browser/ui/cocoa/extensions/browser_actions_controller.h" 35#import "chrome/browser/ui/cocoa/gradient_button_cell.h" 36#import "chrome/browser/ui/cocoa/location_bar/autocomplete_text_field_editor.h" 37#import "chrome/browser/ui/cocoa/location_bar/location_bar_view_mac.h" 38#import "chrome/browser/ui/cocoa/menu_button.h" 39#import "chrome/browser/ui/cocoa/menu_controller.h" 40#import "chrome/browser/ui/cocoa/toolbar/back_forward_menu_controller.h" 41#import "chrome/browser/ui/cocoa/toolbar/reload_button.h" 42#import "chrome/browser/ui/cocoa/toolbar/toolbar_view.h" 43#import "chrome/browser/ui/cocoa/view_id_util.h" 44#import "chrome/browser/ui/cocoa/wrench_menu/wrench_menu_controller.h" 45#include "chrome/browser/ui/toolbar/toolbar_model.h" 46#include "chrome/browser/ui/toolbar/wrench_menu_model.h" 47#include "chrome/common/notification_details.h" 48#include "chrome/common/notification_observer.h" 49#include "chrome/common/notification_service.h" 50#include "chrome/common/notification_type.h" 51#include "chrome/common/pref_names.h" 52#include "grit/chromium_strings.h" 53#include "grit/generated_resources.h" 54#include "grit/theme_resources.h" 55#include "ui/base/l10n/l10n_util_mac.h" 56#include "ui/base/models/accelerator_cocoa.h" 57#include "ui/base/models/menu_model.h" 58#include "ui/base/resource/resource_bundle.h" 59#include "ui/gfx/rect.h" 60 61namespace { 62 63// Names of images in the bundle for buttons. 64NSString* const kBackButtonImageName = @"back_Template.pdf"; 65NSString* const kForwardButtonImageName = @"forward_Template.pdf"; 66NSString* const kReloadButtonReloadImageName = @"reload_Template.pdf"; 67NSString* const kReloadButtonStopImageName = @"stop_Template.pdf"; 68NSString* const kHomeButtonImageName = @"home_Template.pdf"; 69NSString* const kWrenchButtonImageName = @"tools_Template.pdf"; 70 71// Height of the toolbar in pixels when the bookmark bar is closed. 72const CGFloat kBaseToolbarHeight = 35.0; 73 74// The minimum width of the location bar in pixels. 75const CGFloat kMinimumLocationBarWidth = 100.0; 76 77// The duration of any animation that occurs within the toolbar in seconds. 78const CGFloat kAnimationDuration = 0.2; 79 80// The amount of left padding that the wrench menu should have. 81const CGFloat kWrenchMenuLeftPadding = 3.0; 82 83} // namespace 84 85@interface ToolbarController(Private) 86- (void)addAccessibilityDescriptions; 87- (void)initCommandStatus:(CommandUpdater*)commands; 88- (void)prefChanged:(std::string*)prefName; 89- (BackgroundGradientView*)backgroundGradientView; 90- (void)toolbarFrameChanged; 91- (void)pinLocationBarToLeftOfBrowserActionsContainerAndAnimate:(BOOL)animate; 92- (void)maintainMinimumLocationBarWidth; 93- (void)adjustBrowserActionsContainerForNewWindow:(NSNotification*)notification; 94- (void)browserActionsContainerDragged:(NSNotification*)notification; 95- (void)browserActionsContainerDragFinished:(NSNotification*)notification; 96- (void)browserActionsVisibilityChanged:(NSNotification*)notification; 97- (void)adjustLocationSizeBy:(CGFloat)dX animate:(BOOL)animate; 98- (void)badgeWrenchMenuIfNeeded; 99@end 100 101namespace ToolbarControllerInternal { 102 103// A C++ delegate that handles the accelerators in the wrench menu. 104class WrenchAcceleratorDelegate : public ui::AcceleratorProvider { 105 public: 106 virtual bool GetAcceleratorForCommandId(int command_id, 107 ui::Accelerator* accelerator_generic) { 108 // Downcast so that when the copy constructor is invoked below, the key 109 // string gets copied, too. 110 ui::AcceleratorCocoa* out_accelerator = 111 static_cast<ui::AcceleratorCocoa*>(accelerator_generic); 112 AcceleratorsCocoa* keymap = AcceleratorsCocoa::GetInstance(); 113 const ui::AcceleratorCocoa* accelerator = 114 keymap->GetAcceleratorForCommand(command_id); 115 if (accelerator) { 116 *out_accelerator = *accelerator; 117 return true; 118 } 119 return false; 120 } 121}; 122 123// A class registered for C++ notifications. This is used to detect changes in 124// preferences and upgrade available notifications. Bridges the notification 125// back to the ToolbarController. 126class NotificationBridge : public NotificationObserver { 127 public: 128 explicit NotificationBridge(ToolbarController* controller) 129 : controller_(controller) { 130 registrar_.Add(this, NotificationType::UPGRADE_RECOMMENDED, 131 NotificationService::AllSources()); 132 registrar_.Add(this, 133 NotificationType::BACKGROUND_PAGE_TRACKER_CHANGED, 134 NotificationService::AllSources()); 135 } 136 137 // Overridden from NotificationObserver: 138 virtual void Observe(NotificationType type, 139 const NotificationSource& source, 140 const NotificationDetails& details) { 141 switch (type.value) { 142 case NotificationType::PREF_CHANGED: 143 [controller_ prefChanged:Details<std::string>(details).ptr()]; 144 break; 145 case NotificationType::BACKGROUND_PAGE_TRACKER_CHANGED: 146 // fall-through 147 case NotificationType::UPGRADE_RECOMMENDED: 148 [controller_ badgeWrenchMenuIfNeeded]; 149 break; 150 default: 151 NOTREACHED(); 152 } 153 } 154 155 private: 156 ToolbarController* controller_; // weak, owns us 157 158 NotificationRegistrar registrar_; 159}; 160 161} // namespace ToolbarControllerInternal 162 163@implementation ToolbarController 164 165- (id)initWithModel:(ToolbarModel*)model 166 commands:(CommandUpdater*)commands 167 profile:(Profile*)profile 168 browser:(Browser*)browser 169 resizeDelegate:(id<ViewResizer>)resizeDelegate 170 nibFileNamed:(NSString*)nibName { 171 DCHECK(model && commands && profile && [nibName length]); 172 if ((self = [super initWithNibName:nibName 173 bundle:base::mac::MainAppBundle()])) { 174 toolbarModel_ = model; 175 commands_ = commands; 176 profile_ = profile; 177 browser_ = browser; 178 resizeDelegate_ = resizeDelegate; 179 hasToolbar_ = YES; 180 hasLocationBar_ = YES; 181 182 // Register for notifications about state changes for the toolbar buttons 183 commandObserver_.reset(new CommandObserverBridge(self, commands)); 184 commandObserver_->ObserveCommand(IDC_BACK); 185 commandObserver_->ObserveCommand(IDC_FORWARD); 186 commandObserver_->ObserveCommand(IDC_RELOAD); 187 commandObserver_->ObserveCommand(IDC_HOME); 188 commandObserver_->ObserveCommand(IDC_BOOKMARK_PAGE); 189 } 190 return self; 191} 192 193- (id)initWithModel:(ToolbarModel*)model 194 commands:(CommandUpdater*)commands 195 profile:(Profile*)profile 196 browser:(Browser*)browser 197 resizeDelegate:(id<ViewResizer>)resizeDelegate { 198 if ((self = [self initWithModel:model 199 commands:commands 200 profile:profile 201 browser:browser 202 resizeDelegate:resizeDelegate 203 nibFileNamed:@"Toolbar"])) { 204 } 205 return self; 206} 207 208 209- (void)dealloc { 210 // Unset ViewIDs of toolbar elements. 211 // ViewIDs of |toolbarView|, |reloadButton_|, |locationBar_| and 212 // |browserActionsContainerView_| are handled by themselves. 213 view_id_util::UnsetID(backButton_); 214 view_id_util::UnsetID(forwardButton_); 215 view_id_util::UnsetID(homeButton_); 216 view_id_util::UnsetID(wrenchButton_); 217 218 // Make sure any code in the base class which assumes [self view] is 219 // the "parent" view continues to work. 220 hasToolbar_ = YES; 221 hasLocationBar_ = YES; 222 223 [[NSNotificationCenter defaultCenter] removeObserver:self]; 224 225 if (trackingArea_.get()) 226 [[self view] removeTrackingArea:trackingArea_.get()]; 227 [super dealloc]; 228} 229 230// Called after the view is done loading and the outlets have been hooked up. 231// Now we can hook up bridges that rely on UI objects such as the location 232// bar and button state. 233- (void)awakeFromNib { 234 // A bug in AppKit (<rdar://7298597>, <http://openradar.me/7298597>) causes 235 // images loaded directly from nibs in a framework to not get their "template" 236 // flags set properly. Thus, despite the images being set on the buttons in 237 // the xib, we must set them in code. 238 [backButton_ setImage:app::mac::GetCachedImageWithName(kBackButtonImageName)]; 239 [forwardButton_ setImage: 240 app::mac::GetCachedImageWithName(kForwardButtonImageName)]; 241 [reloadButton_ setImage: 242 app::mac::GetCachedImageWithName(kReloadButtonReloadImageName)]; 243 [homeButton_ setImage: 244 app::mac::GetCachedImageWithName(kHomeButtonImageName)]; 245 [wrenchButton_ setImage: 246 app::mac::GetCachedImageWithName(kWrenchButtonImageName)]; 247 [self badgeWrenchMenuIfNeeded]; 248 249 [wrenchButton_ setOpenMenuOnClick:YES]; 250 251 [backButton_ setShowsBorderOnlyWhileMouseInside:YES]; 252 [forwardButton_ setShowsBorderOnlyWhileMouseInside:YES]; 253 [reloadButton_ setShowsBorderOnlyWhileMouseInside:YES]; 254 [homeButton_ setShowsBorderOnlyWhileMouseInside:YES]; 255 [wrenchButton_ setShowsBorderOnlyWhileMouseInside:YES]; 256 257 [self initCommandStatus:commands_]; 258 locationBarView_.reset(new LocationBarViewMac(locationBar_, 259 commands_, toolbarModel_, 260 profile_, browser_)); 261 [locationBar_ setFont:[NSFont systemFontOfSize:[NSFont systemFontSize]]]; 262 // Register pref observers for the optional home and page/options buttons 263 // and then add them to the toolbar based on those prefs. 264 notificationBridge_.reset( 265 new ToolbarControllerInternal::NotificationBridge(self)); 266 PrefService* prefs = profile_->GetPrefs(); 267 showHomeButton_.Init(prefs::kShowHomeButton, prefs, 268 notificationBridge_.get()); 269 showPageOptionButtons_.Init(prefs::kShowPageOptionsButtons, prefs, 270 notificationBridge_.get()); 271 [self showOptionalHomeButton]; 272 [self installWrenchMenu]; 273 274 // Create the controllers for the back/forward menus. 275 backMenuController_.reset([[BackForwardMenuController alloc] 276 initWithBrowser:browser_ 277 modelType:BACK_FORWARD_MENU_TYPE_BACK 278 button:backButton_]); 279 forwardMenuController_.reset([[BackForwardMenuController alloc] 280 initWithBrowser:browser_ 281 modelType:BACK_FORWARD_MENU_TYPE_FORWARD 282 button:forwardButton_]); 283 284 // For a popup window, the toolbar is really just a location bar 285 // (see override for [ToolbarController view], below). When going 286 // fullscreen, we remove the toolbar controller's view from the view 287 // hierarchy. Calling [locationBar_ removeFromSuperview] when going 288 // fullscreen causes it to get released, making us unhappy 289 // (http://crbug.com/18551). We avoid the problem by incrementing 290 // the retain count of the location bar; use of the scoped object 291 // helps us remember to release it. 292 locationBarRetainer_.reset([locationBar_ retain]); 293 trackingArea_.reset( 294 [[NSTrackingArea alloc] initWithRect:NSZeroRect // Ignored 295 options:NSTrackingMouseMoved | 296 NSTrackingInVisibleRect | 297 NSTrackingMouseEnteredAndExited | 298 NSTrackingActiveAlways 299 owner:self 300 userInfo:nil]); 301 NSView* toolbarView = [self view]; 302 [toolbarView addTrackingArea:trackingArea_.get()]; 303 304 // If the user has any Browser Actions installed, the container view for them 305 // may have to be resized depending on the width of the toolbar frame. 306 [toolbarView setPostsFrameChangedNotifications:YES]; 307 [[NSNotificationCenter defaultCenter] 308 addObserver:self 309 selector:@selector(toolbarFrameChanged) 310 name:NSViewFrameDidChangeNotification 311 object:toolbarView]; 312 313 // Set ViewIDs for toolbar elements which don't have their dedicated class. 314 // ViewIDs of |toolbarView|, |reloadButton_|, |locationBar_| and 315 // |browserActionsContainerView_| are handled by themselves. 316 view_id_util::SetID(backButton_, VIEW_ID_BACK_BUTTON); 317 view_id_util::SetID(forwardButton_, VIEW_ID_FORWARD_BUTTON); 318 view_id_util::SetID(homeButton_, VIEW_ID_HOME_BUTTON); 319 view_id_util::SetID(wrenchButton_, VIEW_ID_APP_MENU); 320 321 [self addAccessibilityDescriptions]; 322} 323 324- (void)addAccessibilityDescriptions { 325 // Set accessibility descriptions. http://openradar.appspot.com/7496255 326 NSString* description = l10n_util::GetNSStringWithFixup(IDS_ACCNAME_BACK); 327 [[backButton_ cell] 328 accessibilitySetOverrideValue:description 329 forAttribute:NSAccessibilityDescriptionAttribute]; 330 description = l10n_util::GetNSStringWithFixup(IDS_ACCNAME_FORWARD); 331 [[forwardButton_ cell] 332 accessibilitySetOverrideValue:description 333 forAttribute:NSAccessibilityDescriptionAttribute]; 334 description = l10n_util::GetNSStringWithFixup(IDS_ACCNAME_RELOAD); 335 [[reloadButton_ cell] 336 accessibilitySetOverrideValue:description 337 forAttribute:NSAccessibilityDescriptionAttribute]; 338 description = l10n_util::GetNSStringWithFixup(IDS_ACCNAME_HOME); 339 [[homeButton_ cell] 340 accessibilitySetOverrideValue:description 341 forAttribute:NSAccessibilityDescriptionAttribute]; 342 description = l10n_util::GetNSStringWithFixup(IDS_ACCNAME_LOCATION); 343 [[locationBar_ cell] 344 accessibilitySetOverrideValue:description 345 forAttribute:NSAccessibilityDescriptionAttribute]; 346 description = l10n_util::GetNSStringWithFixup(IDS_ACCNAME_APP); 347 [[wrenchButton_ cell] 348 accessibilitySetOverrideValue:description 349 forAttribute:NSAccessibilityDescriptionAttribute]; 350} 351 352- (void)mouseExited:(NSEvent*)theEvent { 353 [[hoveredButton_ cell] setMouseInside:NO animate:YES]; 354 [hoveredButton_ release]; 355 hoveredButton_ = nil; 356} 357 358- (NSButton*)hoverButtonForEvent:(NSEvent*)theEvent { 359 NSButton* targetView = (NSButton*)[[self view] 360 hitTest:[theEvent locationInWindow]]; 361 362 // Only interpret the view as a hoverButton_ if it's both button and has a 363 // button cell that cares. GradientButtonCell derived cells care. 364 if (([targetView isKindOfClass:[NSButton class]]) && 365 ([[targetView cell] 366 respondsToSelector:@selector(setMouseInside:animate:)])) 367 return targetView; 368 return nil; 369} 370 371- (void)mouseMoved:(NSEvent*)theEvent { 372 NSButton* targetView = [self hoverButtonForEvent:theEvent]; 373 if (hoveredButton_ != targetView) { 374 [[hoveredButton_ cell] setMouseInside:NO animate:YES]; 375 [[targetView cell] setMouseInside:YES animate:YES]; 376 [hoveredButton_ release]; 377 hoveredButton_ = [targetView retain]; 378 } 379} 380 381- (void)mouseEntered:(NSEvent*)event { 382 [self mouseMoved:event]; 383} 384 385- (LocationBarViewMac*)locationBarBridge { 386 return locationBarView_.get(); 387} 388 389- (void)focusLocationBar:(BOOL)selectAll { 390 if (locationBarView_.get()) 391 locationBarView_->FocusLocation(selectAll ? true : false); 392} 393 394// Called when the state for a command changes to |enabled|. Update the 395// corresponding UI element. 396- (void)enabledStateChangedForCommand:(NSInteger)command enabled:(BOOL)enabled { 397 NSButton* button = nil; 398 switch (command) { 399 case IDC_BACK: 400 button = backButton_; 401 break; 402 case IDC_FORWARD: 403 button = forwardButton_; 404 break; 405 case IDC_HOME: 406 button = homeButton_; 407 break; 408 } 409 [button setEnabled:enabled]; 410} 411 412// Init the enabled state of the buttons on the toolbar to match the state in 413// the controller. 414- (void)initCommandStatus:(CommandUpdater*)commands { 415 [backButton_ setEnabled:commands->IsCommandEnabled(IDC_BACK) ? YES : NO]; 416 [forwardButton_ 417 setEnabled:commands->IsCommandEnabled(IDC_FORWARD) ? YES : NO]; 418 [reloadButton_ setEnabled:YES]; 419 [homeButton_ setEnabled:commands->IsCommandEnabled(IDC_HOME) ? YES : NO]; 420} 421 422- (void)updateToolbarWithContents:(TabContents*)tab 423 shouldRestoreState:(BOOL)shouldRestore { 424 locationBarView_->Update(tab, shouldRestore ? true : false); 425 426 [locationBar_ updateCursorAndToolTipRects]; 427 428 if (browserActionsController_.get()) { 429 [browserActionsController_ update]; 430 } 431} 432 433- (void)setStarredState:(BOOL)isStarred { 434 locationBarView_->SetStarred(isStarred ? true : false); 435} 436 437- (void)setIsLoading:(BOOL)isLoading force:(BOOL)force { 438 [reloadButton_ setIsLoading:isLoading force:force]; 439} 440 441- (void)setHasToolbar:(BOOL)toolbar hasLocationBar:(BOOL)locBar { 442 [self view]; // Force nib loading. 443 444 hasToolbar_ = toolbar; 445 446 // If there's a toolbar, there must be a location bar. 447 DCHECK((toolbar && locBar) || !toolbar); 448 hasLocationBar_ = toolbar ? YES : locBar; 449 450 // Decide whether to hide/show based on whether there's a location bar. 451 [[self view] setHidden:!hasLocationBar_]; 452 453 // Make location bar not editable when in a pop-up. 454 locationBarView_->SetEditable(toolbar); 455} 456 457- (NSView*)view { 458 if (hasToolbar_) 459 return [super view]; 460 return locationBar_; 461} 462 463// (Private) Returns the backdrop to the toolbar. 464- (BackgroundGradientView*)backgroundGradientView { 465 // We really do mean |[super view]|; see our override of |-view|. 466 DCHECK([[super view] isKindOfClass:[BackgroundGradientView class]]); 467 return (BackgroundGradientView*)[super view]; 468} 469 470- (id)customFieldEditorForObject:(id)obj { 471 if (obj == locationBar_) { 472 // Lazilly construct Field editor, Cocoa UI code always runs on the 473 // same thread, so there shoudn't be a race condition here. 474 if (autocompleteTextFieldEditor_.get() == nil) { 475 autocompleteTextFieldEditor_.reset( 476 [[AutocompleteTextFieldEditor alloc] init]); 477 } 478 479 // This needs to be called every time, otherwise notifications 480 // aren't sent correctly. 481 DCHECK(autocompleteTextFieldEditor_.get()); 482 [autocompleteTextFieldEditor_.get() setFieldEditor:YES]; 483 return autocompleteTextFieldEditor_.get(); 484 } 485 return nil; 486} 487 488// Returns an array of views in the order of the outlets above. 489- (NSArray*)toolbarViews { 490 return [NSArray arrayWithObjects:backButton_, forwardButton_, reloadButton_, 491 homeButton_, wrenchButton_, locationBar_, 492 browserActionsContainerView_, nil]; 493} 494 495// Moves |rect| to the right by |delta|, keeping the right side fixed by 496// shrinking the width to compensate. Passing a negative value for |deltaX| 497// moves to the left and increases the width. 498- (NSRect)adjustRect:(NSRect)rect byAmount:(CGFloat)deltaX { 499 NSRect frame = NSOffsetRect(rect, deltaX, 0); 500 frame.size.width -= deltaX; 501 return frame; 502} 503 504// Show or hide the home button based on the pref. 505- (void)showOptionalHomeButton { 506 // Ignore this message if only showing the URL bar. 507 if (!hasToolbar_) 508 return; 509 BOOL hide = showHomeButton_.GetValue() ? NO : YES; 510 if (hide == [homeButton_ isHidden]) 511 return; // Nothing to do, view state matches pref state. 512 513 // Always shift the text field by the width of the home button minus one pixel 514 // since the frame edges of each button are right on top of each other. When 515 // hiding the button, reverse the direction of the movement (to the left). 516 CGFloat moveX = [homeButton_ frame].size.width - 1.0; 517 if (hide) 518 moveX *= -1; // Reverse the direction of the move. 519 520 [locationBar_ setFrame:[self adjustRect:[locationBar_ frame] 521 byAmount:moveX]]; 522 [homeButton_ setHidden:hide]; 523} 524 525// Install the menu wrench buttons. Calling this repeatedly is inexpensive so it 526// can be done every time the buttons are shown. 527- (void)installWrenchMenu { 528 if (wrenchMenuModel_.get()) 529 return; 530 acceleratorDelegate_.reset( 531 new ToolbarControllerInternal::WrenchAcceleratorDelegate()); 532 533 wrenchMenuModel_.reset(new WrenchMenuModel( 534 acceleratorDelegate_.get(), browser_)); 535 [wrenchMenuController_ setModel:wrenchMenuModel_.get()]; 536 [wrenchMenuController_ setUseWithPopUpButtonCell:YES]; 537 [wrenchButton_ setAttachedMenu:[wrenchMenuController_ menu]]; 538} 539 540- (WrenchMenuController*)wrenchMenuController { 541 return wrenchMenuController_; 542} 543 544- (void)badgeWrenchMenuIfNeeded { 545 546 int badgeResource = 0; 547 if (UpgradeDetector::GetInstance()->notify_upgrade()) { 548 badgeResource = IDR_UPDATE_BADGE; 549 } else if (BackgroundPageTracker::GetInstance()-> 550 GetUnacknowledgedBackgroundPageCount() > 0) { 551 badgeResource = IDR_BACKGROUND_BADGE; 552 } else { 553 // No badge - clear the badge if one is already set. 554 if ([[wrenchButton_ cell] overlayImage]) 555 [[wrenchButton_ cell] setOverlayImage:nil]; 556 return; 557 } 558 559 NSImage* badge = 560 ResourceBundle::GetSharedInstance().GetNativeImageNamed(badgeResource); 561 NSImage* wrenchImage = 562 app::mac::GetCachedImageWithName(kWrenchButtonImageName); 563 NSSize wrenchImageSize = [wrenchImage size]; 564 NSSize badgeSize = [badge size]; 565 566 scoped_nsobject<NSImage> overlayImage( 567 [[NSImage alloc] initWithSize:wrenchImageSize]); 568 569 // Draw badge in the upper right corner of the button. 570 NSPoint overlayPosition = 571 NSMakePoint(wrenchImageSize.width - badgeSize.width, 572 wrenchImageSize.height - badgeSize.height); 573 574 [overlayImage lockFocus]; 575 [badge drawAtPoint:overlayPosition 576 fromRect:NSZeroRect 577 operation:NSCompositeSourceOver 578 fraction:1.0]; 579 [overlayImage unlockFocus]; 580 581 [[wrenchButton_ cell] setOverlayImage:overlayImage]; 582} 583 584- (void)prefChanged:(std::string*)prefName { 585 if (!prefName) return; 586 if (*prefName == prefs::kShowHomeButton) { 587 [self showOptionalHomeButton]; 588 } 589} 590 591- (void)createBrowserActionButtons { 592 if (!browserActionsController_.get()) { 593 browserActionsController_.reset([[BrowserActionsController alloc] 594 initWithBrowser:browser_ 595 containerView:browserActionsContainerView_]); 596 [[NSNotificationCenter defaultCenter] 597 addObserver:self 598 selector:@selector(browserActionsContainerDragged:) 599 name:kBrowserActionGrippyDraggingNotification 600 object:browserActionsController_]; 601 [[NSNotificationCenter defaultCenter] 602 addObserver:self 603 selector:@selector(browserActionsContainerDragFinished:) 604 name:kBrowserActionGrippyDragFinishedNotification 605 object:browserActionsController_]; 606 [[NSNotificationCenter defaultCenter] 607 addObserver:self 608 selector:@selector(browserActionsVisibilityChanged:) 609 name:kBrowserActionVisibilityChangedNotification 610 object:browserActionsController_]; 611 [[NSNotificationCenter defaultCenter] 612 addObserver:self 613 selector:@selector(adjustBrowserActionsContainerForNewWindow:) 614 name:NSWindowDidBecomeKeyNotification 615 object:[[self view] window]]; 616 } 617 CGFloat containerWidth = [browserActionsContainerView_ isHidden] ? 0.0 : 618 NSWidth([browserActionsContainerView_ frame]); 619 if (containerWidth > 0.0) 620 [self adjustLocationSizeBy:(containerWidth * -1) animate:NO]; 621} 622 623- (void)adjustBrowserActionsContainerForNewWindow: 624 (NSNotification*)notification { 625 [self toolbarFrameChanged]; 626 [[NSNotificationCenter defaultCenter] 627 removeObserver:self 628 name:NSWindowDidBecomeKeyNotification 629 object:[[self view] window]]; 630} 631 632- (void)browserActionsContainerDragged:(NSNotification*)notification { 633 CGFloat locationBarWidth = NSWidth([locationBar_ frame]); 634 locationBarAtMinSize_ = locationBarWidth <= kMinimumLocationBarWidth; 635 [browserActionsContainerView_ setCanDragLeft:!locationBarAtMinSize_]; 636 [browserActionsContainerView_ setGrippyPinned:locationBarAtMinSize_]; 637 [self adjustLocationSizeBy: 638 [browserActionsContainerView_ resizeDeltaX] animate:NO]; 639} 640 641- (void)browserActionsContainerDragFinished:(NSNotification*)notification { 642 [browserActionsController_ resizeContainerAndAnimate:YES]; 643 [self pinLocationBarToLeftOfBrowserActionsContainerAndAnimate:YES]; 644} 645 646- (void)browserActionsVisibilityChanged:(NSNotification*)notification { 647 [self pinLocationBarToLeftOfBrowserActionsContainerAndAnimate:NO]; 648} 649 650- (void)pinLocationBarToLeftOfBrowserActionsContainerAndAnimate:(BOOL)animate { 651 CGFloat locationBarXPos = NSMaxX([locationBar_ frame]); 652 CGFloat leftDistance; 653 654 if ([browserActionsContainerView_ isHidden]) { 655 CGFloat edgeXPos = [wrenchButton_ frame].origin.x; 656 leftDistance = edgeXPos - locationBarXPos - kWrenchMenuLeftPadding; 657 } else { 658 NSRect containerFrame = animate ? 659 [browserActionsContainerView_ animationEndFrame] : 660 [browserActionsContainerView_ frame]; 661 662 leftDistance = containerFrame.origin.x - locationBarXPos; 663 } 664 if (leftDistance != 0.0) 665 [self adjustLocationSizeBy:leftDistance animate:animate]; 666} 667 668- (void)maintainMinimumLocationBarWidth { 669 CGFloat locationBarWidth = NSWidth([locationBar_ frame]); 670 locationBarAtMinSize_ = locationBarWidth <= kMinimumLocationBarWidth; 671 if (locationBarAtMinSize_) { 672 CGFloat dX = kMinimumLocationBarWidth - locationBarWidth; 673 [self adjustLocationSizeBy:dX animate:NO]; 674 } 675} 676 677- (void)toolbarFrameChanged { 678 // Do nothing if the frame changes but no Browser Action Controller is 679 // present. 680 if (!browserActionsController_.get()) 681 return; 682 683 [self maintainMinimumLocationBarWidth]; 684 685 if (locationBarAtMinSize_) { 686 // Once the grippy is pinned, leave it until it is explicity un-pinned. 687 [browserActionsContainerView_ setGrippyPinned:YES]; 688 NSRect containerFrame = [browserActionsContainerView_ frame]; 689 // Determine how much the container needs to move in case it's overlapping 690 // with the location bar. 691 CGFloat dX = NSMaxX([locationBar_ frame]) - containerFrame.origin.x; 692 containerFrame = NSOffsetRect(containerFrame, dX, 0); 693 containerFrame.size.width -= dX; 694 [browserActionsContainerView_ setFrame:containerFrame]; 695 } else if (!locationBarAtMinSize_ && 696 [browserActionsContainerView_ grippyPinned]) { 697 // Expand out the container until it hits the saved size, then unpin the 698 // grippy. 699 // Add 0.1 pixel so that it doesn't hit the minimum width codepath above. 700 CGFloat dX = NSWidth([locationBar_ frame]) - 701 (kMinimumLocationBarWidth + 0.1); 702 NSRect containerFrame = [browserActionsContainerView_ frame]; 703 containerFrame = NSOffsetRect(containerFrame, -dX, 0); 704 containerFrame.size.width += dX; 705 CGFloat savedContainerWidth = [browserActionsController_ savedWidth]; 706 if (NSWidth(containerFrame) >= savedContainerWidth) { 707 containerFrame = NSOffsetRect(containerFrame, 708 NSWidth(containerFrame) - savedContainerWidth, 0); 709 containerFrame.size.width = savedContainerWidth; 710 [browserActionsContainerView_ setGrippyPinned:NO]; 711 } 712 [browserActionsContainerView_ setFrame:containerFrame]; 713 [self pinLocationBarToLeftOfBrowserActionsContainerAndAnimate:NO]; 714 } 715} 716 717- (void)adjustLocationSizeBy:(CGFloat)dX animate:(BOOL)animate { 718 // Ensure that the location bar is in its proper place. 719 NSRect locationFrame = [locationBar_ frame]; 720 locationFrame.size.width += dX; 721 722 if (!animate) { 723 [locationBar_ setFrame:locationFrame]; 724 return; 725 } 726 727 [NSAnimationContext beginGrouping]; 728 [[NSAnimationContext currentContext] setDuration:kAnimationDuration]; 729 [[locationBar_ animator] setFrame:locationFrame]; 730 [NSAnimationContext endGrouping]; 731} 732 733- (NSPoint)bookmarkBubblePoint { 734 return locationBarView_->GetBookmarkBubblePoint(); 735} 736 737- (CGFloat)desiredHeightForCompression:(CGFloat)compressByHeight { 738 // With no toolbar, just ignore the compression. 739 return hasToolbar_ ? kBaseToolbarHeight - compressByHeight : 740 NSHeight([locationBar_ frame]); 741} 742 743- (void)setDividerOpacity:(CGFloat)opacity { 744 BackgroundGradientView* view = [self backgroundGradientView]; 745 [view setShowsDivider:(opacity > 0 ? YES : NO)]; 746 747 // We may not have a toolbar view (e.g., popup windows only have a location 748 // bar). 749 if ([view isKindOfClass:[ToolbarView class]]) { 750 ToolbarView* toolbarView = (ToolbarView*)view; 751 [toolbarView setDividerOpacity:opacity]; 752 } 753} 754 755- (BrowserActionsController*)browserActionsController { 756 return browserActionsController_.get(); 757} 758 759// (URLDropTargetController protocol) 760- (void)dropURLs:(NSArray*)urls inView:(NSView*)view at:(NSPoint)point { 761 // TODO(viettrungluu): This code is more or less copied from the code in 762 // |TabStripController|. I'll refactor this soon to make it common and expand 763 // its capabilities (e.g., allow text DnD). 764 if ([urls count] < 1) { 765 NOTREACHED(); 766 return; 767 } 768 769 // TODO(viettrungluu): dropping multiple URLs? 770 if ([urls count] > 1) 771 NOTIMPLEMENTED(); 772 773 // Get the first URL and fix it up. 774 GURL url(URLFixerUpper::FixupURL( 775 base::SysNSStringToUTF8([urls objectAtIndex:0]), std::string())); 776 777 browser_->GetSelectedTabContents()->OpenURL(url, GURL(), CURRENT_TAB, 778 PageTransition::TYPED); 779} 780 781// (URLDropTargetController protocol) 782- (void)dropText:(NSString*)text inView:(NSView*)view at:(NSPoint)point { 783 // TODO(viettrungluu): This code is more or less copied from the code in 784 // |TabStripController|. I'll refactor this soon to make it common and expand 785 // its capabilities (e.g., allow text DnD). 786 787 // If the input is plain text, classify the input and make the URL. 788 AutocompleteMatch match; 789 browser_->profile()->GetAutocompleteClassifier()->Classify( 790 base::SysNSStringToUTF16(text), string16(), false, &match, NULL); 791 GURL url(match.destination_url); 792 793 browser_->GetSelectedTabContents()->OpenURL(url, GURL(), CURRENT_TAB, 794 PageTransition::TYPED); 795} 796 797// (URLDropTargetController protocol) 798- (void)indicateDropURLsInView:(NSView*)view at:(NSPoint)point { 799 // Do nothing. 800} 801 802// (URLDropTargetController protocol) 803- (void)hideDropURLsIndicatorInView:(NSView*)view { 804 // Do nothing. 805} 806 807@end 808