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 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)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