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