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