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