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