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