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/toolbar_model.h" 50 #include "chrome/browser/ui/toolbar/wrench_menu_model.h" 51 #include "chrome/browser/upgrade_detector.h" 52 #include "chrome/common/net/url_fixer_upper.h" 53 #include "chrome/common/pref_names.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 68 using content::OpenURLParams; 69 using content::Referrer; 70 using content::WebContents; 71 72 namespace { 73 74 // Height of the toolbar in pixels when the bookmark bar is closed. 75 const CGFloat kBaseToolbarHeightNormal = 35.0; 76 77 // The minimum width of the location bar in pixels. 78 const CGFloat kMinimumLocationBarWidth = 100.0; 79 80 // The duration of any animation that occurs within the toolbar in seconds. 81 const CGFloat kAnimationDuration = 0.2; 82 83 // The amount of left padding that the wrench menu should have. 84 const 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 105 namespace 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. 110 class 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)initWithModel:(ToolbarModel*)model 152 commands:(CommandUpdater*)commands 153 profile:(Profile*)profile 154 browser:(Browser*)browser 155 resizeDelegate:(id<ViewResizer>)resizeDelegate 156 nibFileNamed:(NSString*)nibName { 157 DCHECK(model && commands && profile && [nibName length]); 158 if ((self = [super initWithNibName:nibName 159 bundle:base::mac::FrameworkBundle()])) { 160 toolbarModel_ = model; 161 commands_ = commands; 162 profile_ = profile; 163 browser_ = browser; 164 resizeDelegate_ = resizeDelegate; 165 hasToolbar_ = YES; 166 hasLocationBar_ = YES; 167 168 // Register for notifications about state changes for the toolbar buttons 169 commandObserver_.reset(new CommandObserverBridge(self, commands)); 170 commandObserver_->ObserveCommand(IDC_BACK); 171 commandObserver_->ObserveCommand(IDC_FORWARD); 172 commandObserver_->ObserveCommand(IDC_RELOAD); 173 commandObserver_->ObserveCommand(IDC_HOME); 174 commandObserver_->ObserveCommand(IDC_BOOKMARK_PAGE); 175 } 176 return self; 177 } 178 179 - (id)initWithModel:(ToolbarModel*)model 180 commands:(CommandUpdater*)commands 181 profile:(Profile*)profile 182 browser:(Browser*)browser 183 resizeDelegate:(id<ViewResizer>)resizeDelegate { 184 if ((self = [self initWithModel:model 185 commands:commands 186 profile:profile 187 browser:browser 188 resizeDelegate:resizeDelegate 189 nibFileNamed:@"Toolbar"])) { 190 } 191 return self; 192 } 193 194 195 - (void)dealloc { 196 // Unset ViewIDs of toolbar elements. 197 // ViewIDs of |toolbarView|, |reloadButton_|, |locationBar_| and 198 // |browserActionsContainerView_| are handled by themselves. 199 view_id_util::UnsetID(backButton_); 200 view_id_util::UnsetID(forwardButton_); 201 view_id_util::UnsetID(homeButton_); 202 view_id_util::UnsetID(wrenchButton_); 203 204 // Make sure any code in the base class which assumes [self view] is 205 // the "parent" view continues to work. 206 hasToolbar_ = YES; 207 hasLocationBar_ = YES; 208 209 [[NSNotificationCenter defaultCenter] removeObserver:self]; 210 211 if (trackingArea_.get()) 212 [[self view] removeTrackingArea:trackingArea_.get()]; 213 [super dealloc]; 214 } 215 216 // Called after the view is done loading and the outlets have been hooked up. 217 // Now we can hook up bridges that rely on UI objects such as the location 218 // bar and button state. 219 - (void)awakeFromNib { 220 [[backButton_ cell] setImageID:IDR_BACK 221 forButtonState:image_button_cell::kDefaultState]; 222 [[backButton_ cell] setImageID:IDR_BACK_H 223 forButtonState:image_button_cell::kHoverState]; 224 [[backButton_ cell] setImageID:IDR_BACK_P 225 forButtonState:image_button_cell::kPressedState]; 226 [[backButton_ cell] setImageID:IDR_BACK_D 227 forButtonState:image_button_cell::kDisabledState]; 228 229 [[forwardButton_ cell] setImageID:IDR_FORWARD 230 forButtonState:image_button_cell::kDefaultState]; 231 [[forwardButton_ cell] setImageID:IDR_FORWARD_H 232 forButtonState:image_button_cell::kHoverState]; 233 [[forwardButton_ cell] setImageID:IDR_FORWARD_P 234 forButtonState:image_button_cell::kPressedState]; 235 [[forwardButton_ cell] setImageID:IDR_FORWARD_D 236 forButtonState:image_button_cell::kDisabledState]; 237 238 [[reloadButton_ cell] setImageID:IDR_RELOAD 239 forButtonState:image_button_cell::kDefaultState]; 240 [[reloadButton_ cell] setImageID:IDR_RELOAD_H 241 forButtonState:image_button_cell::kHoverState]; 242 [[reloadButton_ cell] setImageID:IDR_RELOAD_P 243 forButtonState:image_button_cell::kPressedState]; 244 245 [[homeButton_ cell] setImageID:IDR_HOME 246 forButtonState:image_button_cell::kDefaultState]; 247 [[homeButton_ cell] setImageID:IDR_HOME_H 248 forButtonState:image_button_cell::kHoverState]; 249 [[homeButton_ cell] setImageID:IDR_HOME_P 250 forButtonState:image_button_cell::kPressedState]; 251 252 [[wrenchButton_ cell] setImageID:IDR_TOOLS 253 forButtonState:image_button_cell::kDefaultState]; 254 [[wrenchButton_ cell] setImageID:IDR_TOOLS_H 255 forButtonState:image_button_cell::kHoverState]; 256 [[wrenchButton_ cell] setImageID:IDR_TOOLS_P 257 forButtonState:image_button_cell::kPressedState]; 258 259 [self updateWrenchButtonSeverity]; 260 261 [wrenchButton_ setOpenMenuOnClick:YES]; 262 263 [backButton_ setOpenMenuOnRightClick:YES]; 264 [forwardButton_ setOpenMenuOnRightClick:YES]; 265 266 [backButton_ setHandleMiddleClick:YES]; 267 [forwardButton_ setHandleMiddleClick:YES]; 268 [reloadButton_ setHandleMiddleClick:YES]; 269 [homeButton_ setHandleMiddleClick:YES]; 270 271 [self initCommandStatus:commands_]; 272 273 locationBarView_.reset(new LocationBarViewMac(locationBar_, 274 commands_, toolbarModel_, 275 profile_, browser_)); 276 [locationBar_ setFont:[NSFont systemFontOfSize:[NSFont systemFontSize]]]; 277 // Register pref observers for the optional home and page/options buttons 278 // and then add them to the toolbar based on those prefs. 279 notificationBridge_.reset( 280 new ToolbarControllerInternal::NotificationBridge(self)); 281 PrefService* prefs = profile_->GetPrefs(); 282 showHomeButton_.Init( 283 prefs::kShowHomeButton, prefs, 284 base::Bind( 285 &ToolbarControllerInternal::NotificationBridge::OnPreferenceChanged, 286 base::Unretained(notificationBridge_.get()))); 287 [self showOptionalHomeButton]; 288 [self installWrenchMenu]; 289 290 // Create the controllers for the back/forward menus. 291 backMenuController_.reset([[BackForwardMenuController alloc] 292 initWithBrowser:browser_ 293 modelType:BACK_FORWARD_MENU_TYPE_BACK 294 button:backButton_]); 295 forwardMenuController_.reset([[BackForwardMenuController alloc] 296 initWithBrowser:browser_ 297 modelType:BACK_FORWARD_MENU_TYPE_FORWARD 298 button:forwardButton_]); 299 300 // For a popup window, the toolbar is really just a location bar 301 // (see override for [ToolbarController view], below). When going 302 // fullscreen, we remove the toolbar controller's view from the view 303 // hierarchy. Calling [locationBar_ removeFromSuperview] when going 304 // fullscreen causes it to get released, making us unhappy 305 // (http://crbug.com/18551). We avoid the problem by incrementing 306 // the retain count of the location bar; use of the scoped object 307 // helps us remember to release it. 308 locationBarRetainer_.reset([locationBar_ retain]); 309 trackingArea_.reset( 310 [[CrTrackingArea alloc] initWithRect:NSZeroRect // Ignored 311 options:NSTrackingMouseMoved | 312 NSTrackingInVisibleRect | 313 NSTrackingMouseEnteredAndExited | 314 NSTrackingActiveAlways 315 owner:self 316 userInfo:nil]); 317 NSView* toolbarView = [self view]; 318 [toolbarView addTrackingArea:trackingArea_.get()]; 319 320 // If the user has any Browser Actions installed, the container view for them 321 // may have to be resized depending on the width of the toolbar frame. 322 [toolbarView setPostsFrameChangedNotifications:YES]; 323 [[NSNotificationCenter defaultCenter] 324 addObserver:self 325 selector:@selector(toolbarFrameChanged) 326 name:NSViewFrameDidChangeNotification 327 object:toolbarView]; 328 329 // Set ViewIDs for toolbar elements which don't have their dedicated class. 330 // ViewIDs of |toolbarView|, |reloadButton_|, |locationBar_| and 331 // |browserActionsContainerView_| are handled by themselves. 332 view_id_util::SetID(backButton_, VIEW_ID_BACK_BUTTON); 333 view_id_util::SetID(forwardButton_, VIEW_ID_FORWARD_BUTTON); 334 view_id_util::SetID(homeButton_, VIEW_ID_HOME_BUTTON); 335 view_id_util::SetID(wrenchButton_, VIEW_ID_APP_MENU); 336 337 [self addAccessibilityDescriptions]; 338 } 339 340 - (void)addAccessibilityDescriptions { 341 // Set accessibility descriptions. http://openradar.appspot.com/7496255 342 NSString* description = l10n_util::GetNSStringWithFixup(IDS_ACCNAME_BACK); 343 [[backButton_ cell] 344 accessibilitySetOverrideValue:description 345 forAttribute:NSAccessibilityDescriptionAttribute]; 346 description = l10n_util::GetNSStringWithFixup(IDS_ACCNAME_FORWARD); 347 [[forwardButton_ cell] 348 accessibilitySetOverrideValue:description 349 forAttribute:NSAccessibilityDescriptionAttribute]; 350 description = l10n_util::GetNSStringWithFixup(IDS_ACCNAME_RELOAD); 351 [[reloadButton_ cell] 352 accessibilitySetOverrideValue:description 353 forAttribute:NSAccessibilityDescriptionAttribute]; 354 description = l10n_util::GetNSStringWithFixup(IDS_ACCNAME_HOME); 355 [[homeButton_ cell] 356 accessibilitySetOverrideValue:description 357 forAttribute:NSAccessibilityDescriptionAttribute]; 358 description = l10n_util::GetNSStringWithFixup(IDS_ACCNAME_LOCATION); 359 [[locationBar_ cell] 360 accessibilitySetOverrideValue:description 361 forAttribute:NSAccessibilityDescriptionAttribute]; 362 description = l10n_util::GetNSStringWithFixup(IDS_ACCNAME_APP); 363 [[wrenchButton_ cell] 364 accessibilitySetOverrideValue:description 365 forAttribute:NSAccessibilityDescriptionAttribute]; 366 } 367 368 - (void)mouseExited:(NSEvent*)theEvent { 369 [[hoveredButton_ cell] setMouseInside:NO animate:YES]; 370 [hoveredButton_ release]; 371 hoveredButton_ = nil; 372 } 373 374 - (NSButton*)hoverButtonForEvent:(NSEvent*)theEvent { 375 NSButton* targetView = (NSButton*)[[self view] 376 hitTest:[theEvent locationInWindow]]; 377 378 // Only interpret the view as a hoverButton_ if it's both button and has a 379 // button cell that cares. GradientButtonCell derived cells care. 380 if (([targetView isKindOfClass:[NSButton class]]) && 381 ([[targetView cell] 382 respondsToSelector:@selector(setMouseInside:animate:)])) 383 return targetView; 384 return nil; 385 } 386 387 - (void)mouseMoved:(NSEvent*)theEvent { 388 NSButton* targetView = [self hoverButtonForEvent:theEvent]; 389 if (hoveredButton_ != targetView) { 390 [[hoveredButton_ cell] setMouseInside:NO animate:YES]; 391 [[targetView cell] setMouseInside:YES animate:YES]; 392 [hoveredButton_ release]; 393 hoveredButton_ = [targetView retain]; 394 } 395 } 396 397 - (void)mouseEntered:(NSEvent*)event { 398 [self mouseMoved:event]; 399 } 400 401 - (LocationBarViewMac*)locationBarBridge { 402 return locationBarView_.get(); 403 } 404 405 - (void)focusLocationBar:(BOOL)selectAll { 406 if (locationBarView_.get()) 407 locationBarView_->FocusLocation(selectAll ? true : false); 408 } 409 410 // Called when the state for a command changes to |enabled|. Update the 411 // corresponding UI element. 412 - (void)enabledStateChangedForCommand:(NSInteger)command enabled:(BOOL)enabled { 413 NSButton* button = nil; 414 switch (command) { 415 case IDC_BACK: 416 button = backButton_; 417 break; 418 case IDC_FORWARD: 419 button = forwardButton_; 420 break; 421 case IDC_HOME: 422 button = homeButton_; 423 break; 424 } 425 [button setEnabled:enabled]; 426 } 427 428 // Init the enabled state of the buttons on the toolbar to match the state in 429 // the controller. 430 - (void)initCommandStatus:(CommandUpdater*)commands { 431 [backButton_ setEnabled:commands->IsCommandEnabled(IDC_BACK) ? YES : NO]; 432 [forwardButton_ 433 setEnabled:commands->IsCommandEnabled(IDC_FORWARD) ? YES : NO]; 434 [reloadButton_ setEnabled:YES]; 435 [homeButton_ setEnabled:commands->IsCommandEnabled(IDC_HOME) ? YES : NO]; 436 } 437 438 - (void)updateToolbarWithContents:(WebContents*)tab 439 shouldRestoreState:(BOOL)shouldRestore { 440 locationBarView_->Update(tab, shouldRestore ? true : false); 441 442 [locationBar_ updateMouseTracking]; 443 444 if (browserActionsController_.get()) { 445 [browserActionsController_ update]; 446 } 447 } 448 449 - (void)setStarredState:(BOOL)isStarred { 450 locationBarView_->SetStarred(isStarred ? true : false); 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 return autocompleteTextFieldEditor_.get(); 505 } 506 return nil; 507 } 508 509 // Returns an array of views in the order of the outlets above. 510 - (NSArray*)toolbarViews { 511 return [NSArray arrayWithObjects:backButton_, forwardButton_, reloadButton_, 512 homeButton_, wrenchButton_, locationBar_, 513 browserActionsContainerView_, nil]; 514 } 515 516 // Moves |rect| to the right by |delta|, keeping the right side fixed by 517 // shrinking the width to compensate. Passing a negative value for |deltaX| 518 // moves to the left and increases the width. 519 - (NSRect)adjustRect:(NSRect)rect byAmount:(CGFloat)deltaX { 520 NSRect frame = NSOffsetRect(rect, deltaX, 0); 521 frame.size.width -= deltaX; 522 return frame; 523 } 524 525 // Show or hide the home button based on the pref. 526 - (void)showOptionalHomeButton { 527 // Ignore this message if only showing the URL bar. 528 if (!hasToolbar_) 529 return; 530 BOOL hide = showHomeButton_.GetValue() ? NO : YES; 531 if (hide == [homeButton_ isHidden]) 532 return; // Nothing to do, view state matches pref state. 533 534 // Always shift the text field by the width of the home button minus one pixel 535 // since the frame edges of each button are right on top of each other. When 536 // hiding the button, reverse the direction of the movement (to the left). 537 CGFloat moveX = [homeButton_ frame].size.width - 1.0; 538 if (hide) 539 moveX *= -1; // Reverse the direction of the move. 540 541 [locationBar_ setFrame:[self adjustRect:[locationBar_ frame] 542 byAmount:moveX]]; 543 [homeButton_ setHidden:hide]; 544 } 545 546 // Install the menu wrench buttons. Calling this repeatedly is inexpensive so it 547 // can be done every time the buttons are shown. 548 - (void)installWrenchMenu { 549 if (wrenchMenuController_.get()) 550 return; 551 552 wrenchMenuController_.reset( 553 [[WrenchMenuController alloc] initWithBrowser:browser_]); 554 [wrenchMenuController_ setUseWithPopUpButtonCell:YES]; 555 [wrenchButton_ setAttachedMenu:[wrenchMenuController_ menu]]; 556 } 557 558 - (WrenchMenuController*)wrenchMenuController { 559 return wrenchMenuController_; 560 } 561 562 - (void)updateWrenchButtonSeverity { 563 WrenchToolbarButtonCell* cell = 564 base::mac::ObjCCastStrict<WrenchToolbarButtonCell>([wrenchButton_ cell]); 565 if (UpgradeDetector::GetInstance()->notify_upgrade()) { 566 UpgradeDetector::UpgradeNotificationAnnoyanceLevel level = 567 UpgradeDetector::GetInstance()->upgrade_notification_stage(); 568 [cell setSeverity:WrenchIconPainter::SeverityFromUpgradeLevel(level) 569 shouldAnimate:WrenchIconPainter::ShouldAnimateUpgradeLevel(level)]; 570 return; 571 } 572 573 GlobalError* error = GlobalErrorServiceFactory::GetForProfile( 574 browser_->profile())->GetHighestSeverityGlobalErrorWithWrenchMenuItem(); 575 if (error) { 576 [cell setSeverity:WrenchIconPainter::GlobalErrorSeverity() 577 shouldAnimate:YES]; 578 return; 579 } 580 581 [cell setSeverity:WrenchIconPainter::SEVERITY_NONE shouldAnimate:YES]; 582 } 583 584 - (void)prefChanged:(const std::string&)prefName { 585 if (prefName == prefs::kShowHomeButton) { 586 [self showOptionalHomeButton]; 587 } 588 } 589 590 - (void)createBrowserActionButtons { 591 if (!browserActionsController_.get()) { 592 browserActionsController_.reset([[BrowserActionsController alloc] 593 initWithBrowser:browser_ 594 containerView:browserActionsContainerView_]); 595 [[NSNotificationCenter defaultCenter] 596 addObserver:self 597 selector:@selector(browserActionsContainerDragged:) 598 name:kBrowserActionGrippyDraggingNotification 599 object:browserActionsController_]; 600 [[NSNotificationCenter defaultCenter] 601 addObserver:self 602 selector:@selector(browserActionsContainerDragFinished:) 603 name:kBrowserActionGrippyDragFinishedNotification 604 object:browserActionsController_]; 605 [[NSNotificationCenter defaultCenter] 606 addObserver:self 607 selector:@selector(browserActionsVisibilityChanged:) 608 name:kBrowserActionVisibilityChangedNotification 609 object:browserActionsController_]; 610 [[NSNotificationCenter defaultCenter] 611 addObserver:self 612 selector:@selector(adjustBrowserActionsContainerForNewWindow:) 613 name:NSWindowDidBecomeKeyNotification 614 object:[[self view] window]]; 615 } 616 CGFloat containerWidth = [browserActionsContainerView_ isHidden] ? 0.0 : 617 NSWidth([browserActionsContainerView_ frame]); 618 if (containerWidth > 0.0) 619 [self adjustLocationSizeBy:(containerWidth * -1) animate:NO]; 620 } 621 622 - (void)adjustBrowserActionsContainerForNewWindow: 623 (NSNotification*)notification { 624 [self toolbarFrameChanged]; 625 [[NSNotificationCenter defaultCenter] 626 removeObserver:self 627 name:NSWindowDidBecomeKeyNotification 628 object:[[self view] window]]; 629 } 630 631 - (void)browserActionsContainerDragged:(NSNotification*)notification { 632 CGFloat locationBarWidth = NSWidth([locationBar_ frame]); 633 locationBarAtMinSize_ = locationBarWidth <= kMinimumLocationBarWidth; 634 [browserActionsContainerView_ setCanDragLeft:!locationBarAtMinSize_]; 635 [browserActionsContainerView_ setGrippyPinned:locationBarAtMinSize_]; 636 [self adjustLocationSizeBy: 637 [browserActionsContainerView_ resizeDeltaX] animate:NO]; 638 } 639 640 - (void)browserActionsContainerDragFinished:(NSNotification*)notification { 641 [browserActionsController_ resizeContainerAndAnimate:YES]; 642 [self pinLocationBarToLeftOfBrowserActionsContainerAndAnimate:YES]; 643 } 644 645 - (void)browserActionsVisibilityChanged:(NSNotification*)notification { 646 [self pinLocationBarToLeftOfBrowserActionsContainerAndAnimate:NO]; 647 } 648 649 - (void)pinLocationBarToLeftOfBrowserActionsContainerAndAnimate:(BOOL)animate { 650 CGFloat locationBarXPos = NSMaxX([locationBar_ frame]); 651 CGFloat leftDistance; 652 653 if ([browserActionsContainerView_ isHidden]) { 654 CGFloat edgeXPos = [wrenchButton_ frame].origin.x; 655 leftDistance = edgeXPos - locationBarXPos - kWrenchMenuLeftPadding; 656 } else { 657 NSRect containerFrame = animate ? 658 [browserActionsContainerView_ animationEndFrame] : 659 [browserActionsContainerView_ frame]; 660 661 leftDistance = containerFrame.origin.x - locationBarXPos; 662 } 663 if (leftDistance != 0.0) 664 [self adjustLocationSizeBy:leftDistance animate:animate]; 665 } 666 667 - (void)maintainMinimumLocationBarWidth { 668 CGFloat locationBarWidth = NSWidth([locationBar_ frame]); 669 locationBarAtMinSize_ = locationBarWidth <= kMinimumLocationBarWidth; 670 if (locationBarAtMinSize_) { 671 CGFloat dX = kMinimumLocationBarWidth - locationBarWidth; 672 [self adjustLocationSizeBy:dX animate:NO]; 673 } 674 } 675 676 - (void)toolbarFrameChanged { 677 // Do nothing if the frame changes but no Browser Action Controller is 678 // present. 679 if (!browserActionsController_.get()) 680 return; 681 682 [self maintainMinimumLocationBarWidth]; 683 684 if (locationBarAtMinSize_) { 685 // Once the grippy is pinned, leave it until it is explicity un-pinned. 686 [browserActionsContainerView_ setGrippyPinned:YES]; 687 NSRect containerFrame = [browserActionsContainerView_ frame]; 688 // Determine how much the container needs to move in case it's overlapping 689 // with the location bar. 690 CGFloat dX = NSMaxX([locationBar_ frame]) - containerFrame.origin.x; 691 containerFrame = NSOffsetRect(containerFrame, dX, 0); 692 containerFrame.size.width -= dX; 693 [browserActionsContainerView_ setFrame:containerFrame]; 694 } else if (!locationBarAtMinSize_ && 695 [browserActionsContainerView_ grippyPinned]) { 696 // Expand out the container until it hits the saved size, then unpin the 697 // grippy. 698 // Add 0.1 pixel so that it doesn't hit the minimum width codepath above. 699 CGFloat dX = NSWidth([locationBar_ frame]) - 700 (kMinimumLocationBarWidth + 0.1); 701 NSRect containerFrame = [browserActionsContainerView_ frame]; 702 containerFrame = NSOffsetRect(containerFrame, -dX, 0); 703 containerFrame.size.width += dX; 704 CGFloat savedContainerWidth = [browserActionsController_ savedWidth]; 705 if (NSWidth(containerFrame) >= savedContainerWidth) { 706 containerFrame = NSOffsetRect(containerFrame, 707 NSWidth(containerFrame) - savedContainerWidth, 0); 708 containerFrame.size.width = savedContainerWidth; 709 [browserActionsContainerView_ setGrippyPinned:NO]; 710 } 711 [browserActionsContainerView_ setFrame:containerFrame]; 712 [self pinLocationBarToLeftOfBrowserActionsContainerAndAnimate:NO]; 713 } 714 } 715 716 - (void)adjustLocationSizeBy:(CGFloat)dX animate:(BOOL)animate { 717 // Ensure that the location bar is in its proper place. 718 NSRect locationFrame = [locationBar_ frame]; 719 locationFrame.size.width += dX; 720 721 if (!animate) { 722 [locationBar_ setFrame:locationFrame]; 723 return; 724 } 725 726 [NSAnimationContext beginGrouping]; 727 [[NSAnimationContext currentContext] setDuration:kAnimationDuration]; 728 [[locationBar_ animator] setFrame:locationFrame]; 729 [NSAnimationContext endGrouping]; 730 } 731 732 - (NSPoint)bookmarkBubblePoint { 733 return locationBarView_->GetBookmarkBubblePoint(); 734 } 735 736 - (CGFloat)desiredHeightForCompression:(CGFloat)compressByHeight { 737 // With no toolbar, just ignore the compression. 738 if (!hasToolbar_) 739 return NSHeight([locationBar_ frame]); 740 741 return kBaseToolbarHeightNormal - compressByHeight; 742 } 743 744 - (void)setDividerOpacity:(CGFloat)opacity { 745 BackgroundGradientView* view = [self backgroundGradientView]; 746 [view setShowsDivider:(opacity > 0 ? YES : NO)]; 747 748 // We may not have a toolbar view (e.g., popup windows only have a location 749 // bar). 750 if ([view isKindOfClass:[ToolbarView class]]) { 751 ToolbarView* toolbarView = (ToolbarView*)view; 752 [toolbarView setDividerOpacity:opacity]; 753 } 754 755 [view setNeedsDisplay:YES]; 756 } 757 758 - (BrowserActionsController*)browserActionsController { 759 return browserActionsController_.get(); 760 } 761 762 - (NSView*)wrenchButton { 763 return wrenchButton_; 764 } 765 766 // (URLDropTargetController protocol) 767 - (void)dropURLs:(NSArray*)urls inView:(NSView*)view at:(NSPoint)point { 768 // TODO(viettrungluu): This code is more or less copied from the code in 769 // |TabStripController|. I'll refactor this soon to make it common and expand 770 // its capabilities (e.g., allow text DnD). 771 if ([urls count] < 1) { 772 NOTREACHED(); 773 return; 774 } 775 776 // TODO(viettrungluu): dropping multiple URLs? 777 if ([urls count] > 1) 778 NOTIMPLEMENTED(); 779 780 // Get the first URL and fix it up. 781 GURL url(URLFixerUpper::FixupURL( 782 base::SysNSStringToUTF8([urls objectAtIndex:0]), std::string())); 783 784 if (url.SchemeIs(chrome::kJavaScriptScheme)) { 785 browser_->window()->GetLocationBar()->GetLocationEntry()->SetUserText( 786 OmniboxView::StripJavascriptSchemas(UTF8ToUTF16(url.spec()))); 787 } 788 OpenURLParams params( 789 url, Referrer(), CURRENT_TAB, content::PAGE_TRANSITION_TYPED, false); 790 browser_->tab_strip_model()->GetActiveWebContents()->OpenURL(params); 791 } 792 793 // (URLDropTargetController protocol) 794 - (void)dropText:(NSString*)text inView:(NSView*)view at:(NSPoint)point { 795 // TODO(viettrungluu): This code is more or less copied from the code in 796 // |TabStripController|. I'll refactor this soon to make it common and expand 797 // its capabilities (e.g., allow text DnD). 798 799 // If the input is plain text, classify the input and make the URL. 800 AutocompleteMatch match; 801 AutocompleteClassifierFactory::GetForProfile(browser_->profile())->Classify( 802 base::SysNSStringToUTF16(text), false, false, &match, NULL); 803 GURL url(match.destination_url); 804 805 OpenURLParams params( 806 url, Referrer(), CURRENT_TAB, content::PAGE_TRANSITION_TYPED, false); 807 browser_->tab_strip_model()->GetActiveWebContents()->OpenURL(params); 808 } 809 810 // (URLDropTargetController protocol) 811 - (void)indicateDropURLsInView:(NSView*)view at:(NSPoint)point { 812 // Do nothing. 813 } 814 815 // (URLDropTargetController protocol) 816 - (void)hideDropURLsIndicatorInView:(NSView*)view { 817 // Do nothing. 818 } 819 820 // (URLDropTargetController protocol) 821 - (BOOL)isUnsupportedDropData:(id<NSDraggingInfo>)info { 822 return drag_util::IsUnsupportedDropData(profile_, info); 823 } 824 825 @end 826