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