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