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