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