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