1 // Copyright (c) 2012 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 "base/mac/bundle_locations.h" 10 #include "base/mac/mac_util.h" 11 #include "base/memory/singleton.h" 12 #include "base/prefs/pref_service.h" 13 #include "base/strings/string_util.h" 14 #include "base/strings/sys_string_conversions.h" 15 #include "base/strings/utf_string_conversions.h" 16 #include "chrome/app/chrome_command_ids.h" 17 #include "chrome/browser/autocomplete/autocomplete_classifier.h" 18 #include "chrome/browser/autocomplete/autocomplete_classifier_factory.h" 19 #include "chrome/browser/chrome_notification_types.h" 20 #include "chrome/browser/command_updater.h" 21 #include "chrome/browser/profiles/profile.h" 22 #include "chrome/browser/search/search.h" 23 #include "chrome/browser/themes/theme_service.h" 24 #include "chrome/browser/ui/browser.h" 25 #include "chrome/browser/ui/browser_window.h" 26 #import "chrome/browser/ui/cocoa/background_gradient_view.h" 27 #include "chrome/browser/ui/cocoa/drag_util.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/image_button_cell.h" 33 #import "chrome/browser/ui/cocoa/location_bar/autocomplete_text_field_editor.h" 34 #import "chrome/browser/ui/cocoa/location_bar/location_bar_view_mac.h" 35 #import "chrome/browser/ui/cocoa/menu_button.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/toolbar/wrench_toolbar_button_cell.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/omnibox/omnibox_view.h" 44 #include "chrome/browser/ui/tabs/tab_strip_model.h" 45 #include "chrome/browser/ui/toolbar/wrench_menu_badge_controller.h" 46 #include "chrome/browser/ui/toolbar/wrench_menu_model.h" 47 #include "chrome/common/pref_names.h" 48 #include "chrome/grit/chromium_strings.h" 49 #include "chrome/grit/generated_resources.h" 50 #include "components/metrics/proto/omnibox_event.pb.h" 51 #include "components/omnibox/autocomplete_match.h" 52 #include "components/search_engines/template_url_service.h" 53 #include "components/url_fixer/url_fixer.h" 54 #include "content/public/browser/web_contents.h" 55 #include "grit/theme_resources.h" 56 #import "ui/base/cocoa/menu_controller.h" 57 #include "ui/base/l10n/l10n_util.h" 58 #include "ui/base/l10n/l10n_util_mac.h" 59 #include "ui/gfx/image/image.h" 60 #include "ui/gfx/rect.h" 61 62 using content::OpenURLParams; 63 using content::Referrer; 64 using content::WebContents; 65 66 namespace { 67 68 // Height of the toolbar in pixels when the bookmark bar is closed. 69 const CGFloat kBaseToolbarHeightNormal = 35.0; 70 71 // The minimum width of the location bar in pixels. 72 const CGFloat kMinimumLocationBarWidth = 100.0; 73 74 // The duration of any animation that occurs within the toolbar in seconds. 75 const CGFloat kAnimationDuration = 0.2; 76 77 // The amount of left padding that the wrench menu should have. 78 const CGFloat kWrenchMenuLeftPadding = 3.0; 79 80 } // namespace 81 82 @interface ToolbarController() 83 @property(assign, nonatomic) Browser* browser; 84 - (void)addAccessibilityDescriptions; 85 - (void)initCommandStatus:(CommandUpdater*)commands; 86 - (void)prefChanged:(const std::string&)prefName; 87 - (BackgroundGradientView*)backgroundGradientView; 88 - (void)toolbarFrameChanged; 89 - (void)pinLocationBarToLeftOfBrowserActionsContainerAndAnimate:(BOOL)animate; 90 - (void)maintainMinimumLocationBarWidth; 91 - (void)adjustBrowserActionsContainerForNewWindow:(NSNotification*)notification; 92 - (void)browserActionsContainerDragged:(NSNotification*)notification; 93 - (void)browserActionsContainerDragFinished:(NSNotification*)notification; 94 - (void)browserActionsVisibilityChanged:(NSNotification*)notification; 95 - (void)adjustLocationSizeBy:(CGFloat)dX animate:(BOOL)animate; 96 - (void)updateWrenchButtonSeverity:(WrenchIconPainter::Severity)severity 97 animate:(BOOL)animate; 98 @end 99 100 namespace ToolbarControllerInternal { 101 102 // A class registered for C++ notifications. This is used to detect changes in 103 // preferences and upgrade available notifications. Bridges the notification 104 // back to the ToolbarController. 105 class NotificationBridge : public WrenchMenuBadgeController::Delegate { 106 public: 107 explicit NotificationBridge(ToolbarController* controller) 108 : controller_(controller), 109 badge_controller_([controller browser]->profile(), this) { 110 } 111 virtual ~NotificationBridge() { 112 } 113 114 void UpdateBadgeSeverity() { 115 badge_controller_.UpdateDelegate(); 116 } 117 118 virtual void UpdateBadgeSeverity(WrenchMenuBadgeController::BadgeType type, 119 WrenchIconPainter::Severity severity, 120 bool animate) OVERRIDE { 121 [controller_ updateWrenchButtonSeverity:severity animate:animate]; 122 } 123 124 void OnPreferenceChanged(const std::string& pref_name) { 125 [controller_ prefChanged:pref_name]; 126 } 127 128 private: 129 ToolbarController* controller_; // weak, owns us 130 131 WrenchMenuBadgeController badge_controller_; 132 133 DISALLOW_COPY_AND_ASSIGN(NotificationBridge); 134 }; 135 136 } // namespace ToolbarControllerInternal 137 138 @implementation ToolbarController 139 140 @synthesize browser = browser_; 141 142 - (id)initWithCommands:(CommandUpdater*)commands 143 profile:(Profile*)profile 144 browser:(Browser*)browser 145 resizeDelegate:(id<ViewResizer>)resizeDelegate 146 nibFileNamed:(NSString*)nibName { 147 DCHECK(commands && profile && [nibName length]); 148 if ((self = [super initWithNibName:nibName 149 bundle:base::mac::FrameworkBundle()])) { 150 commands_ = commands; 151 profile_ = profile; 152 browser_ = browser; 153 resizeDelegate_ = resizeDelegate; 154 hasToolbar_ = YES; 155 hasLocationBar_ = YES; 156 157 // Register for notifications about state changes for the toolbar buttons 158 commandObserver_.reset(new CommandObserverBridge(self, commands)); 159 commandObserver_->ObserveCommand(IDC_BACK); 160 commandObserver_->ObserveCommand(IDC_FORWARD); 161 commandObserver_->ObserveCommand(IDC_RELOAD); 162 commandObserver_->ObserveCommand(IDC_HOME); 163 commandObserver_->ObserveCommand(IDC_BOOKMARK_PAGE); 164 } 165 return self; 166 } 167 168 - (id)initWithCommands:(CommandUpdater*)commands 169 profile:(Profile*)profile 170 browser:(Browser*)browser 171 resizeDelegate:(id<ViewResizer>)resizeDelegate { 172 if ((self = [self initWithCommands:commands 173 profile:profile 174 browser:browser 175 resizeDelegate:resizeDelegate 176 nibFileNamed:@"Toolbar"])) { 177 } 178 return self; 179 } 180 181 182 - (void)dealloc { 183 // Unset ViewIDs of toolbar elements. 184 // ViewIDs of |toolbarView|, |reloadButton_|, |locationBar_| and 185 // |browserActionsContainerView_| are handled by themselves. 186 view_id_util::UnsetID(backButton_); 187 view_id_util::UnsetID(forwardButton_); 188 view_id_util::UnsetID(homeButton_); 189 view_id_util::UnsetID(wrenchButton_); 190 191 // Make sure any code in the base class which assumes [self view] is 192 // the "parent" view continues to work. 193 hasToolbar_ = YES; 194 hasLocationBar_ = YES; 195 196 [[NSNotificationCenter defaultCenter] removeObserver:self]; 197 198 if (trackingArea_.get()) 199 [[self view] removeTrackingArea:trackingArea_.get()]; 200 [super dealloc]; 201 } 202 203 // Called after the view is done loading and the outlets have been hooked up. 204 // Now we can hook up bridges that rely on UI objects such as the location 205 // bar and button state. 206 - (void)awakeFromNib { 207 [[backButton_ cell] setImageID:IDR_BACK 208 forButtonState:image_button_cell::kDefaultState]; 209 [[backButton_ cell] setImageID:IDR_BACK_H 210 forButtonState:image_button_cell::kHoverState]; 211 [[backButton_ cell] setImageID:IDR_BACK_P 212 forButtonState:image_button_cell::kPressedState]; 213 [[backButton_ cell] setImageID:IDR_BACK_D 214 forButtonState:image_button_cell::kDisabledState]; 215 216 [[forwardButton_ cell] setImageID:IDR_FORWARD 217 forButtonState:image_button_cell::kDefaultState]; 218 [[forwardButton_ cell] setImageID:IDR_FORWARD_H 219 forButtonState:image_button_cell::kHoverState]; 220 [[forwardButton_ cell] setImageID:IDR_FORWARD_P 221 forButtonState:image_button_cell::kPressedState]; 222 [[forwardButton_ cell] setImageID:IDR_FORWARD_D 223 forButtonState:image_button_cell::kDisabledState]; 224 225 [[reloadButton_ cell] setImageID:IDR_RELOAD 226 forButtonState:image_button_cell::kDefaultState]; 227 [[reloadButton_ cell] setImageID:IDR_RELOAD_H 228 forButtonState:image_button_cell::kHoverState]; 229 [[reloadButton_ cell] setImageID:IDR_RELOAD_P 230 forButtonState:image_button_cell::kPressedState]; 231 232 [[homeButton_ cell] setImageID:IDR_HOME 233 forButtonState:image_button_cell::kDefaultState]; 234 [[homeButton_ cell] setImageID:IDR_HOME_H 235 forButtonState:image_button_cell::kHoverState]; 236 [[homeButton_ cell] setImageID:IDR_HOME_P 237 forButtonState:image_button_cell::kPressedState]; 238 239 [[wrenchButton_ cell] setImageID:IDR_TOOLS 240 forButtonState:image_button_cell::kDefaultState]; 241 [[wrenchButton_ cell] setImageID:IDR_TOOLS_H 242 forButtonState:image_button_cell::kHoverState]; 243 [[wrenchButton_ cell] setImageID:IDR_TOOLS_P 244 forButtonState:image_button_cell::kPressedState]; 245 246 notificationBridge_.reset( 247 new ToolbarControllerInternal::NotificationBridge(self)); 248 notificationBridge_->UpdateBadgeSeverity(); 249 250 [wrenchButton_ setOpenMenuOnClick:YES]; 251 252 [backButton_ setOpenMenuOnRightClick:YES]; 253 [forwardButton_ setOpenMenuOnRightClick:YES]; 254 255 [backButton_ setHandleMiddleClick:YES]; 256 [forwardButton_ setHandleMiddleClick:YES]; 257 [reloadButton_ setHandleMiddleClick:YES]; 258 [homeButton_ setHandleMiddleClick:YES]; 259 260 [self initCommandStatus:commands_]; 261 262 locationBarView_.reset(new LocationBarViewMac(locationBar_, commands_, 263 profile_, browser_)); 264 [locationBar_ setFont:[NSFont systemFontOfSize:[NSFont systemFontSize]]]; 265 266 // Register pref observers for the optional home and page/options buttons 267 // and then add them to the toolbar based on those prefs. 268 PrefService* prefs = profile_->GetPrefs(); 269 showHomeButton_.Init( 270 prefs::kShowHomeButton, prefs, 271 base::Bind( 272 &ToolbarControllerInternal::NotificationBridge::OnPreferenceChanged, 273 base::Unretained(notificationBridge_.get()))); 274 [self showOptionalHomeButton]; 275 [self installWrenchMenu]; 276 277 // Create the controllers for the back/forward menus. 278 backMenuController_.reset([[BackForwardMenuController alloc] 279 initWithBrowser:browser_ 280 modelType:BACK_FORWARD_MENU_TYPE_BACK 281 button:backButton_]); 282 forwardMenuController_.reset([[BackForwardMenuController alloc] 283 initWithBrowser:browser_ 284 modelType:BACK_FORWARD_MENU_TYPE_FORWARD 285 button:forwardButton_]); 286 287 // For a popup window, the toolbar is really just a location bar 288 // (see override for [ToolbarController view], below). When going 289 // fullscreen, we remove the toolbar controller's view from the view 290 // hierarchy. Calling [locationBar_ removeFromSuperview] when going 291 // fullscreen causes it to get released, making us unhappy 292 // (http://crbug.com/18551). We avoid the problem by incrementing 293 // the retain count of the location bar; use of the scoped object 294 // helps us remember to release it. 295 locationBarRetainer_.reset([locationBar_ retain]); 296 trackingArea_.reset( 297 [[CrTrackingArea alloc] initWithRect:NSZeroRect // Ignored 298 options:NSTrackingMouseMoved | 299 NSTrackingInVisibleRect | 300 NSTrackingMouseEnteredAndExited | 301 NSTrackingActiveAlways 302 owner:self 303 userInfo:nil]); 304 NSView* toolbarView = [self view]; 305 [toolbarView addTrackingArea:trackingArea_.get()]; 306 307 // If the user has any Browser Actions installed, the container view for them 308 // may have to be resized depending on the width of the toolbar frame. 309 [toolbarView setPostsFrameChangedNotifications:YES]; 310 [[NSNotificationCenter defaultCenter] 311 addObserver:self 312 selector:@selector(toolbarFrameChanged) 313 name:NSViewFrameDidChangeNotification 314 object:toolbarView]; 315 316 // Set ViewIDs for toolbar elements which don't have their dedicated class. 317 // ViewIDs of |toolbarView|, |reloadButton_|, |locationBar_| and 318 // |browserActionsContainerView_| are handled by themselves. 319 view_id_util::SetID(backButton_, VIEW_ID_BACK_BUTTON); 320 view_id_util::SetID(forwardButton_, VIEW_ID_FORWARD_BUTTON); 321 view_id_util::SetID(homeButton_, VIEW_ID_HOME_BUTTON); 322 view_id_util::SetID(wrenchButton_, VIEW_ID_APP_MENU); 323 324 [self addAccessibilityDescriptions]; 325 } 326 327 - (void)addAccessibilityDescriptions { 328 // Set accessibility descriptions. http://openradar.appspot.com/7496255 329 NSString* description = l10n_util::GetNSStringWithFixup(IDS_ACCNAME_BACK); 330 [[backButton_ cell] 331 accessibilitySetOverrideValue:description 332 forAttribute:NSAccessibilityDescriptionAttribute]; 333 description = l10n_util::GetNSStringWithFixup(IDS_ACCNAME_FORWARD); 334 [[forwardButton_ cell] 335 accessibilitySetOverrideValue:description 336 forAttribute:NSAccessibilityDescriptionAttribute]; 337 description = l10n_util::GetNSStringWithFixup(IDS_ACCNAME_RELOAD); 338 [[reloadButton_ cell] 339 accessibilitySetOverrideValue:description 340 forAttribute:NSAccessibilityDescriptionAttribute]; 341 description = l10n_util::GetNSStringWithFixup(IDS_ACCNAME_HOME); 342 [[homeButton_ cell] 343 accessibilitySetOverrideValue:description 344 forAttribute:NSAccessibilityDescriptionAttribute]; 345 description = l10n_util::GetNSStringWithFixup(IDS_ACCNAME_LOCATION); 346 [[locationBar_ cell] 347 accessibilitySetOverrideValue:description 348 forAttribute:NSAccessibilityDescriptionAttribute]; 349 description = l10n_util::GetNSStringWithFixup(IDS_ACCNAME_APP); 350 [[wrenchButton_ cell] 351 accessibilitySetOverrideValue:description 352 forAttribute:NSAccessibilityDescriptionAttribute]; 353 } 354 355 - (void)mouseExited:(NSEvent*)theEvent { 356 [[hoveredButton_ cell] setIsMouseInside:NO]; 357 [hoveredButton_ release]; 358 hoveredButton_ = nil; 359 } 360 361 - (NSButton*)hoverButtonForEvent:(NSEvent*)theEvent { 362 NSButton* targetView = (NSButton*)[[self view] 363 hitTest:[theEvent locationInWindow]]; 364 365 // Only interpret the view as a hoverButton_ if it's both button and has a 366 // button cell that cares. GradientButtonCell derived cells care. 367 if (([targetView isKindOfClass:[NSButton class]]) && 368 ([[targetView cell] 369 respondsToSelector:@selector(setIsMouseInside:)])) 370 return targetView; 371 return nil; 372 } 373 374 - (void)mouseMoved:(NSEvent*)theEvent { 375 NSButton* targetView = [self hoverButtonForEvent:theEvent]; 376 if (hoveredButton_ != targetView) { 377 [[hoveredButton_ cell] setIsMouseInside:NO]; 378 [[targetView cell] setIsMouseInside:YES]; 379 [hoveredButton_ release]; 380 hoveredButton_ = [targetView retain]; 381 } 382 } 383 384 - (void)mouseEntered:(NSEvent*)event { 385 [self mouseMoved:event]; 386 } 387 388 - (LocationBarViewMac*)locationBarBridge { 389 return locationBarView_.get(); 390 } 391 392 - (void)focusLocationBar:(BOOL)selectAll { 393 if (locationBarView_.get()) { 394 if (selectAll && 395 locationBarView_->GetToolbarModel()->WouldOmitURLDueToOriginChip()) { 396 // select_all is true when it's expected that the user may want to copy 397 // the URL to the clipboard. If the origin chip is being displayed (and 398 // thus the URL is not being shown in the Omnibox) show it now to support 399 // the same functionality. 400 locationBarView_->GetOmniboxView()->ShowURL(); 401 } else { 402 locationBarView_->FocusLocation(selectAll ? true : false); 403 } 404 } 405 } 406 407 // Called when the state for a command changes to |enabled|. Update the 408 // corresponding UI element. 409 - (void)enabledStateChangedForCommand:(NSInteger)command enabled:(BOOL)enabled { 410 NSButton* button = nil; 411 switch (command) { 412 case IDC_BACK: 413 button = backButton_; 414 break; 415 case IDC_FORWARD: 416 button = forwardButton_; 417 break; 418 case IDC_HOME: 419 button = homeButton_; 420 break; 421 } 422 [button setEnabled:enabled]; 423 } 424 425 // Init the enabled state of the buttons on the toolbar to match the state in 426 // the controller. 427 - (void)initCommandStatus:(CommandUpdater*)commands { 428 [backButton_ setEnabled:commands->IsCommandEnabled(IDC_BACK) ? YES : NO]; 429 [forwardButton_ 430 setEnabled:commands->IsCommandEnabled(IDC_FORWARD) ? YES : NO]; 431 [reloadButton_ setEnabled:YES]; 432 [homeButton_ setEnabled:commands->IsCommandEnabled(IDC_HOME) ? YES : NO]; 433 } 434 435 - (void)updateToolbarWithContents:(WebContents*)tab { 436 locationBarView_->Update(tab); 437 438 [locationBar_ updateMouseTracking]; 439 440 if (browserActionsController_.get()) { 441 [browserActionsController_ update]; 442 } 443 } 444 445 - (void)setStarredState:(BOOL)isStarred { 446 locationBarView_->SetStarred(isStarred); 447 } 448 449 - (void)setTranslateIconLit:(BOOL)on { 450 locationBarView_->SetTranslateIconLit(on); 451 } 452 453 - (void)zoomChangedForActiveTab:(BOOL)canShowBubble { 454 locationBarView_->ZoomChangedForActiveTab( 455 canShowBubble && ![wrenchMenuController_ isMenuOpen]); 456 } 457 458 - (void)setIsLoading:(BOOL)isLoading force:(BOOL)force { 459 [reloadButton_ setIsLoading:isLoading force:force]; 460 } 461 462 - (void)setHasToolbar:(BOOL)toolbar hasLocationBar:(BOOL)locBar { 463 [self view]; // Force nib loading. 464 465 hasToolbar_ = toolbar; 466 467 // If there's a toolbar, there must be a location bar. 468 DCHECK((toolbar && locBar) || !toolbar); 469 hasLocationBar_ = toolbar ? YES : locBar; 470 471 // Decide whether to hide/show based on whether there's a location bar. 472 [[self view] setHidden:!hasLocationBar_]; 473 474 // Make location bar not editable when in a pop-up. 475 locationBarView_->SetEditable(toolbar); 476 } 477 478 - (NSView*)view { 479 if (hasToolbar_) 480 return [super view]; 481 return locationBar_; 482 } 483 484 // (Private) Returns the backdrop to the toolbar. 485 - (BackgroundGradientView*)backgroundGradientView { 486 // We really do mean |[super view]|; see our override of |-view|. 487 DCHECK([[super view] isKindOfClass:[BackgroundGradientView class]]); 488 return (BackgroundGradientView*)[super view]; 489 } 490 491 - (id)customFieldEditorForObject:(id)obj { 492 if (obj == locationBar_) { 493 // Lazilly construct Field editor, Cocoa UI code always runs on the 494 // same thread, so there shoudn't be a race condition here. 495 if (autocompleteTextFieldEditor_.get() == nil) { 496 autocompleteTextFieldEditor_.reset( 497 [[AutocompleteTextFieldEditor alloc] init]); 498 } 499 500 // This needs to be called every time, otherwise notifications 501 // aren't sent correctly. 502 DCHECK(autocompleteTextFieldEditor_.get()); 503 [autocompleteTextFieldEditor_.get() setFieldEditor:YES]; 504 if (base::mac::IsOSSnowLeopard()) { 505 // Manually transferring the drawsBackground and backgroundColor 506 // properties is necessary to ensure anti-aliased text on 10.6. 507 [autocompleteTextFieldEditor_ 508 setDrawsBackground:[locationBar_ drawsBackground]]; 509 [autocompleteTextFieldEditor_ 510 setBackgroundColor:[locationBar_ backgroundColor]]; 511 } 512 return autocompleteTextFieldEditor_.get(); 513 } 514 return nil; 515 } 516 517 // Returns an array of views in the order of the outlets above. 518 - (NSArray*)toolbarViews { 519 return [NSArray arrayWithObjects:backButton_, forwardButton_, reloadButton_, 520 homeButton_, wrenchButton_, locationBar_, 521 browserActionsContainerView_, nil]; 522 } 523 524 // Moves |rect| to the right by |delta|, keeping the right side fixed by 525 // shrinking the width to compensate. Passing a negative value for |deltaX| 526 // moves to the left and increases the width. 527 - (NSRect)adjustRect:(NSRect)rect byAmount:(CGFloat)deltaX { 528 NSRect frame = NSOffsetRect(rect, deltaX, 0); 529 frame.size.width -= deltaX; 530 return frame; 531 } 532 533 // Show or hide the home button based on the pref. 534 - (void)showOptionalHomeButton { 535 // Ignore this message if only showing the URL bar. 536 if (!hasToolbar_) 537 return; 538 BOOL hide = showHomeButton_.GetValue() ? NO : YES; 539 if (hide == [homeButton_ isHidden]) 540 return; // Nothing to do, view state matches pref state. 541 542 // Always shift the text field by the width of the home button minus one pixel 543 // since the frame edges of each button are right on top of each other. When 544 // hiding the button, reverse the direction of the movement (to the left). 545 CGFloat moveX = [homeButton_ frame].size.width - 1.0; 546 if (hide) 547 moveX *= -1; // Reverse the direction of the move. 548 549 [locationBar_ setFrame:[self adjustRect:[locationBar_ frame] 550 byAmount:moveX]]; 551 [homeButton_ setHidden:hide]; 552 } 553 554 // Install the menu wrench buttons. Calling this repeatedly is inexpensive so it 555 // can be done every time the buttons are shown. 556 - (void)installWrenchMenu { 557 if (wrenchMenuController_.get()) 558 return; 559 560 wrenchMenuController_.reset( 561 [[WrenchMenuController alloc] initWithBrowser:browser_]); 562 [wrenchMenuController_ setUseWithPopUpButtonCell:YES]; 563 [wrenchButton_ setAttachedMenu:[wrenchMenuController_ menu]]; 564 } 565 566 - (WrenchMenuController*)wrenchMenuController { 567 return wrenchMenuController_; 568 } 569 570 - (void)updateWrenchButtonSeverity:(WrenchIconPainter::Severity)severity 571 animate:(BOOL)animate { 572 WrenchToolbarButtonCell* cell = 573 base::mac::ObjCCastStrict<WrenchToolbarButtonCell>([wrenchButton_ cell]); 574 [cell setSeverity:severity shouldAnimate:animate]; 575 } 576 577 - (void)prefChanged:(const std::string&)prefName { 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 if (locationBarView_->IsStarEnabled()) 727 return locationBarView_->GetBookmarkBubblePoint(); 728 729 // Grab bottom middle of hotdogs. 730 NSRect frame = wrenchButton_.frame; 731 NSPoint point = NSMakePoint(NSMidX(frame), NSMinY(frame)); 732 // Inset to account for the whitespace around the hotdogs. 733 point.y += wrench_menu_controller::kWrenchBubblePointOffsetY; 734 return [self.view convertPoint:point toView:nil]; 735 } 736 737 - (NSPoint)managePasswordsBubblePoint { 738 return locationBarView_->GetManagePasswordsBubblePoint(); 739 } 740 741 - (NSPoint)translateBubblePoint { 742 return locationBarView_->GetTranslateBubblePoint(); 743 } 744 745 - (CGFloat)desiredHeightForCompression:(CGFloat)compressByHeight { 746 // With no toolbar, just ignore the compression. 747 if (!hasToolbar_) 748 return NSHeight([locationBar_ frame]); 749 750 return kBaseToolbarHeightNormal - compressByHeight; 751 } 752 753 - (void)setDividerOpacity:(CGFloat)opacity { 754 BackgroundGradientView* view = [self backgroundGradientView]; 755 [view setShowsDivider:(opacity > 0 ? YES : NO)]; 756 757 // We may not have a toolbar view (e.g., popup windows only have a location 758 // bar). 759 if ([view isKindOfClass:[ToolbarView class]]) { 760 ToolbarView* toolbarView = (ToolbarView*)view; 761 [toolbarView setDividerOpacity:opacity]; 762 } 763 764 [view setNeedsDisplay:YES]; 765 } 766 767 - (BrowserActionsController*)browserActionsController { 768 return browserActionsController_.get(); 769 } 770 771 - (NSView*)wrenchButton { 772 return wrenchButton_; 773 } 774 775 // (URLDropTargetController protocol) 776 - (void)dropURLs:(NSArray*)urls inView:(NSView*)view at:(NSPoint)point { 777 // TODO(viettrungluu): This code is more or less copied from the code in 778 // |TabStripController|. I'll refactor this soon to make it common and expand 779 // its capabilities (e.g., allow text DnD). 780 if ([urls count] < 1) { 781 NOTREACHED(); 782 return; 783 } 784 785 // TODO(viettrungluu): dropping multiple URLs? 786 if ([urls count] > 1) 787 NOTIMPLEMENTED(); 788 789 // Get the first URL and fix it up. 790 GURL url(url_fixer::FixupURL(base::SysNSStringToUTF8([urls objectAtIndex:0]), 791 std::string())); 792 793 if (url.SchemeIs(url::kJavaScriptScheme)) { 794 browser_->window()->GetLocationBar()->GetOmniboxView()->SetUserText( 795 OmniboxView::StripJavascriptSchemas(base::UTF8ToUTF16(url.spec()))); 796 } 797 OpenURLParams params( 798 url, Referrer(), CURRENT_TAB, ui::PAGE_TRANSITION_TYPED, false); 799 browser_->tab_strip_model()->GetActiveWebContents()->OpenURL(params); 800 } 801 802 // (URLDropTargetController protocol) 803 - (void)dropText:(NSString*)text inView:(NSView*)view at:(NSPoint)point { 804 // TODO(viettrungluu): This code is more or less copied from the code in 805 // |TabStripController|. I'll refactor this soon to make it common and expand 806 // its capabilities (e.g., allow text DnD). 807 808 // If the input is plain text, classify the input and make the URL. 809 AutocompleteMatch match; 810 AutocompleteClassifierFactory::GetForProfile(browser_->profile())->Classify( 811 base::SysNSStringToUTF16(text), false, false, 812 metrics::OmniboxEventProto::BLANK, &match, NULL); 813 GURL url(match.destination_url); 814 815 OpenURLParams params( 816 url, Referrer(), CURRENT_TAB, ui::PAGE_TRANSITION_TYPED, false); 817 browser_->tab_strip_model()->GetActiveWebContents()->OpenURL(params); 818 } 819 820 // (URLDropTargetController protocol) 821 - (void)indicateDropURLsInView:(NSView*)view at:(NSPoint)point { 822 // Do nothing. 823 } 824 825 // (URLDropTargetController protocol) 826 - (void)hideDropURLsIndicatorInView:(NSView*)view { 827 // Do nothing. 828 } 829 830 // (URLDropTargetController protocol) 831 - (BOOL)isUnsupportedDropData:(id<NSDraggingInfo>)info { 832 return drag_util::IsUnsupportedDropData(profile_, info); 833 } 834 835 @end 836