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