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