Upstream version 8.37.180.0
[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/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