Make the cocoa BrowserActionsController use the ToolbarActionsBar
[chromium-blink-merge.git] / chrome / browser / ui / cocoa / extensions / browser_actions_controller.mm
blob3970f9680416324f42a071a1067e8cabcdb95bad
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.
5 #import "chrome/browser/ui/cocoa/extensions/browser_actions_controller.h"
7 #include <cmath>
8 #include <string>
10 #include "base/strings/sys_string_conversions.h"
11 #include "chrome/browser/profiles/profile.h"
12 #include "chrome/browser/ui/browser.h"
13 #import "chrome/browser/ui/cocoa/extensions/browser_action_button.h"
14 #import "chrome/browser/ui/cocoa/extensions/browser_actions_container_view.h"
15 #import "chrome/browser/ui/cocoa/extensions/extension_popup_controller.h"
16 #import "chrome/browser/ui/cocoa/image_button_cell.h"
17 #import "chrome/browser/ui/cocoa/menu_button.h"
18 #include "chrome/browser/ui/tabs/tab_strip_model.h"
19 #include "chrome/browser/ui/toolbar/toolbar_action_view_controller.h"
20 #include "chrome/browser/ui/toolbar/toolbar_actions_bar.h"
21 #include "chrome/browser/ui/toolbar/toolbar_actions_bar_delegate.h"
22 #include "grit/theme_resources.h"
23 #import "third_party/google_toolbox_for_mac/src/AppKit/GTMNSAnimation+Duration.h"
25 NSString* const kBrowserActionVisibilityChangedNotification =
26     @"BrowserActionVisibilityChangedNotification";
28 namespace {
30 const CGFloat kAnimationDuration = 0.2;
32 const CGFloat kChevronWidth = 18;
34 // Since the container is the maximum height of the toolbar, we have
35 // to move the buttons up by this amount in order to have them look
36 // vertically centered within the toolbar.
37 const CGFloat kBrowserActionOriginYOffset = 5.0;
39 // How far to inset from the bottom of the view to get the top border
40 // of the popup 2px below the bottom of the Omnibox.
41 const CGFloat kBrowserActionBubbleYOffset = 3.0;
43 }  // namespace
45 @interface BrowserActionsController(Private)
47 // Creates and adds a view for the given |action| at |index|.
48 - (void)addViewForAction:(ToolbarActionViewController*)action
49                withIndex:(NSUInteger)index;
51 // Removes the view for the given |action| from the ccontainer.
52 - (void)removeViewForAction:(ToolbarActionViewController*)action;
54 // Removes views for all actions.
55 - (void)removeAllViews;
57 // Redraws the BrowserActionsContainerView and updates the button order to match
58 // the order in the ToolbarActionsBar.
59 - (void)redraw;
61 // Resizes the container to the specified |width|, optionally animating.
62 - (void)resizeContainerToWidth:(CGFloat)width
63                  shouldAnimate:(BOOL)animate;
65 // Sets the container to be either hidden or visible based on whether there are
66 // any actions to show.
67 // Returns whether the container is visible.
68 - (BOOL)updateContainerVisibility;
70 // During container resizing, buttons become more transparent as they are pushed
71 // off the screen. This method updates each button's opacity determined by the
72 // position of the button.
73 - (void)updateButtonOpacity;
75 // Returns the existing button associated with the given id; nil if it cannot be
76 // found.
77 - (BrowserActionButton*)buttonForId:(const std::string&)id;
79 // Notification handlers for events registered by the class.
81 // Updates each button's opacity, the cursor rects and chevron position.
82 - (void)containerFrameChanged:(NSNotification*)notification;
84 // Hides the chevron and unhides every hidden button so that dragging the
85 // container out smoothly shows the Browser Action buttons.
86 - (void)containerDragStart:(NSNotification*)notification;
88 // Sends a notification for the toolbar to reposition surrounding UI elements.
89 - (void)containerDragging:(NSNotification*)notification;
91 // Determines which buttons need to be hidden based on the new size, hides them
92 // and updates the chevron overflow menu. Also fires a notification to let the
93 // toolbar know that the drag has finished.
94 - (void)containerDragFinished:(NSNotification*)notification;
96 // Sends a notification for the toolbar to determine whether the container can
97 // translate with a delta on x-axis.
98 - (void)containerWillTranslateOnX:(NSNotification*)notification;
100 // Adjusts the position of the surrounding action buttons depending on where the
101 // button is within the container.
102 - (void)actionButtonDragging:(NSNotification*)notification;
104 // Updates the position of the Browser Actions within the container. This fires
105 // when _any_ Browser Action button is done dragging to keep all open windows in
106 // sync visually.
107 - (void)actionButtonDragFinished:(NSNotification*)notification;
109 // Moves the given button both visually and within the toolbar model to the
110 // specified index.
111 - (void)moveButton:(BrowserActionButton*)button
112            toIndex:(NSUInteger)index
113            animate:(BOOL)animate;
115 // Handles clicks for BrowserActionButtons.
116 - (BOOL)browserActionClicked:(BrowserActionButton*)button;
118 // The reason |frame| is specified in these chevron functions is because the
119 // container may be animating and the end frame of the animation should be
120 // passed instead of the current frame (which may be off and cause the chevron
121 // to jump at the end of its animation).
123 // Shows the overflow chevron button depending on whether there are any hidden
124 // extensions within the frame given.
125 - (void)showChevronIfNecessaryInFrame:(NSRect)frame animate:(BOOL)animate;
127 // Moves the chevron to its correct position within |frame|.
128 - (void)updateChevronPositionInFrame:(NSRect)frame;
130 // Shows or hides the chevron, animating as specified by |animate|.
131 - (void)setChevronHidden:(BOOL)hidden
132                  inFrame:(NSRect)frame
133                  animate:(BOOL)animate;
135 // Handles when a menu item within the chevron overflow menu is selected.
136 - (void)chevronItemSelected:(id)menuItem;
138 // Updates the container's grippy cursor based on the number of hidden buttons.
139 - (void)updateGrippyCursors;
141 - (ToolbarActionsBar*)toolbarActionsBar;
143 @end
145 namespace {
147 // A bridge between the ToolbarActionsBar and the BrowserActionsController.
148 class ToolbarActionsBarBridge : public ToolbarActionsBarDelegate {
149  public:
150   explicit ToolbarActionsBarBridge(BrowserActionsController* controller);
151   ~ToolbarActionsBarBridge() override;
153  private:
154   // ToolbarActionsBarDelegate:
155   void AddViewForAction(ToolbarActionViewController* action,
156                         size_t index) override;
157   void RemoveViewForAction(ToolbarActionViewController* action) override;
158   void RemoveAllViews() override;
159   void Redraw(bool order_changed) override;
160   void ResizeAndAnimate(gfx::Tween::Type tween_type,
161                         int target_width,
162                         bool suppress_chevron) override;
163   void SetChevronVisibility(bool chevron_visible) override;
164   int GetWidth() const override;
165   bool IsAnimating() const override;
166   void StopAnimating() override;
167   int GetChevronWidth() const override;
168   bool IsPopupRunning() const override;
170   // The owning BrowserActionsController; weak.
171   BrowserActionsController* controller_;
173   DISALLOW_COPY_AND_ASSIGN(ToolbarActionsBarBridge);
176 ToolbarActionsBarBridge::ToolbarActionsBarBridge(
177     BrowserActionsController* controller)
178     : controller_(controller) {
181 ToolbarActionsBarBridge::~ToolbarActionsBarBridge() {
184 void ToolbarActionsBarBridge::AddViewForAction(
185     ToolbarActionViewController* action,
186     size_t index) {
187   [controller_ addViewForAction:action
188                       withIndex:index];
191 void ToolbarActionsBarBridge::RemoveViewForAction(
192     ToolbarActionViewController* action) {
193   [controller_ removeViewForAction:action];
196 void ToolbarActionsBarBridge::RemoveAllViews() {
197   [controller_ removeAllViews];
200 void ToolbarActionsBarBridge::Redraw(bool order_changed) {
201   [controller_ redraw];
204 void ToolbarActionsBarBridge::ResizeAndAnimate(gfx::Tween::Type tween_type,
205                                                int target_width,
206                                                bool suppress_chevron) {
207   [controller_ resizeContainerToWidth:target_width
208                         shouldAnimate:![controller_ toolbarActionsBar]->
209                                           suppress_animation()];
212 void ToolbarActionsBarBridge::SetChevronVisibility(bool chevron_visible) {
213   [controller_ setChevronHidden:!chevron_visible
214                         inFrame:[[controller_ containerView] frame]
215                         animate:YES];
218 int ToolbarActionsBarBridge::GetWidth() const {
219   return NSWidth([[controller_ containerView] frame]);
222 bool ToolbarActionsBarBridge::IsAnimating() const {
223   return false;
226 void ToolbarActionsBarBridge::StopAnimating() {
229 int ToolbarActionsBarBridge::GetChevronWidth() const {
230   return kChevronWidth;
233 bool ToolbarActionsBarBridge::IsPopupRunning() const {
234   return [ExtensionPopupController popup] != nil;
237 }  // namespace
239 @implementation BrowserActionsController
241 @synthesize containerView = containerView_;
243 #pragma mark -
244 #pragma mark Public Methods
246 - (id)initWithBrowser:(Browser*)browser
247         containerView:(BrowserActionsContainerView*)container {
248   DCHECK(browser && container);
250   if ((self = [super init])) {
251     browser_ = browser;
252     profile_ = browser->profile();
254     toolbarActionsBarBridge_.reset(new ToolbarActionsBarBridge(self));
255     toolbarActionsBar_.reset(
256         new ToolbarActionsBar(toolbarActionsBarBridge_.get(), browser_, false));
258     containerView_ = container;
259     [containerView_ setPostsFrameChangedNotifications:YES];
260     [[NSNotificationCenter defaultCenter]
261         addObserver:self
262            selector:@selector(containerFrameChanged:)
263                name:NSViewFrameDidChangeNotification
264              object:containerView_];
265     [[NSNotificationCenter defaultCenter]
266         addObserver:self
267            selector:@selector(containerDragStart:)
268                name:kBrowserActionGrippyDragStartedNotification
269              object:containerView_];
270     [[NSNotificationCenter defaultCenter]
271         addObserver:self
272            selector:@selector(containerDragging:)
273                name:kBrowserActionGrippyDraggingNotification
274              object:containerView_];
275     [[NSNotificationCenter defaultCenter]
276         addObserver:self
277            selector:@selector(containerDragFinished:)
278                name:kBrowserActionGrippyDragFinishedNotification
279              object:containerView_];
280     [[NSNotificationCenter defaultCenter]
281         addObserver:self
282            selector:@selector(containerWillTranslateOnX:)
283                name:kBrowserActionGrippyWillDragNotification
284              object:containerView_];
285     // Listen for a finished drag from any button to make sure each open window
286     // stays in sync.
287     [[NSNotificationCenter defaultCenter]
288       addObserver:self
289          selector:@selector(actionButtonDragFinished:)
290              name:kBrowserActionButtonDragEndNotification
291            object:nil];
293     suppressChevron_ = NO;
294     chevronAnimation_.reset([[NSViewAnimation alloc] init]);
295     [chevronAnimation_ gtm_setDuration:kAnimationDuration
296                              eventMask:NSLeftMouseUpMask];
297     [chevronAnimation_ setAnimationBlockingMode:NSAnimationNonblocking];
299     hiddenButtons_.reset([[NSMutableArray alloc] init]);
300     buttons_.reset([[NSMutableArray alloc] init]);
301     toolbarActionsBar_->CreateActions();
302     if ([buttons_ count] != 0)
303       [self resizeContainerAndAnimate:NO];
304     [self showChevronIfNecessaryInFrame:[containerView_ frame] animate:NO];
305     [self updateGrippyCursors];
306     [container setResizable:YES];
307   }
309   return self;
312 - (void)dealloc {
313   // Explicitly destroy the ToolbarActionsBar so all buttons get removed with a
314   // valid BrowserActionsController, and so we can verify state before
315   // destruction.
316   toolbarActionsBar_->DeleteActions();
317   toolbarActionsBar_.reset();
318   DCHECK_EQ(0u, [buttons_ count]);
319   [[NSNotificationCenter defaultCenter] removeObserver:self];
320   [super dealloc];
323 - (void)update {
324   for (BrowserActionButton* button in buttons_.get())
325     [button updateState];
328 - (NSUInteger)buttonCount {
329   return [buttons_ count];
332 - (NSUInteger)visibleButtonCount {
333   return [self buttonCount] - [hiddenButtons_ count];
336 - (void)resizeContainerAndAnimate:(BOOL)animate {
337   [self resizeContainerToWidth:toolbarActionsBar_->GetPreferredSize().width()
338                  shouldAnimate:animate];
341 - (CGFloat)savedWidth {
342   return toolbarActionsBar_->GetPreferredSize().width();
345 - (NSPoint)popupPointForId:(const std::string&)id {
346   NSButton* button = [self buttonForId:id];
347   if (!button)
348     return NSZeroPoint;
350   if ([hiddenButtons_ containsObject:button])
351     button = chevronMenuButton_.get();
353   // Anchor point just above the center of the bottom.
354   const NSRect bounds = [button bounds];
355   DCHECK([button isFlipped]);
356   NSPoint anchor = NSMakePoint(NSMidX(bounds),
357                                NSMaxY(bounds) - kBrowserActionBubbleYOffset);
358   return [button convertPoint:anchor toView:nil];
361 - (BOOL)chevronIsHidden {
362   if (!chevronMenuButton_.get())
363     return YES;
365   if (![chevronAnimation_ isAnimating])
366     return [chevronMenuButton_ isHidden];
368   DCHECK([[chevronAnimation_ viewAnimations] count] > 0);
370   // The chevron is animating in or out. Determine which one and have the return
371   // value reflect where the animation is headed.
372   NSString* effect = [[[chevronAnimation_ viewAnimations] objectAtIndex:0]
373       valueForKey:NSViewAnimationEffectKey];
374   if (effect == NSViewAnimationFadeInEffect) {
375     return NO;
376   } else if (effect == NSViewAnimationFadeOutEffect) {
377     return YES;
378   }
380   NOTREACHED();
381   return YES;
384 - (content::WebContents*)currentWebContents {
385   return browser_->tab_strip_model()->GetActiveWebContents();
388 #pragma mark -
389 #pragma mark NSMenuDelegate
391 - (void)menuNeedsUpdate:(NSMenu*)menu {
392   [menu removeAllItems];
394   // See menu_button.h for documentation on why this is needed.
395   [menu addItemWithTitle:@"" action:nil keyEquivalent:@""];
397   for (BrowserActionButton* button in hiddenButtons_.get()) {
398     NSString* name =
399         base::SysUTF16ToNSString([button viewController]->GetActionName());
400     NSMenuItem* item =
401         [menu addItemWithTitle:name
402                         action:@selector(chevronItemSelected:)
403                  keyEquivalent:@""];
404     [item setRepresentedObject:button];
405     [item setImage:[button compositedImage]];
406     [item setTarget:self];
407     [item setEnabled:[button isEnabled]];
408   }
411 #pragma mark -
412 #pragma mark Private Methods
414 - (void)addViewForAction:(ToolbarActionViewController*)action
415                withIndex:(NSUInteger)index {
416   NSRect buttonFrame = NSMakeRect(0.0,
417                                   kBrowserActionOriginYOffset,
418                                   ToolbarActionsBar::IconWidth(false),
419                                   ToolbarActionsBar::IconHeight());
420   BrowserActionButton* newButton =
421       [[[BrowserActionButton alloc]
422          initWithFrame:buttonFrame
423         viewController:action
424             controller:self] autorelease];
425   [newButton setTarget:self];
426   [newButton setAction:@selector(browserActionClicked:)];
427   [buttons_ insertObject:newButton atIndex:index];
430   [[NSNotificationCenter defaultCenter]
431       addObserver:self
432          selector:@selector(actionButtonDragging:)
433              name:kBrowserActionButtonDraggingNotification
434            object:newButton];
437 - (void)redraw {
438   if (![self updateContainerVisibility])
439     return;  // Container is hidden; no need to update.
441   std::vector<ToolbarActionViewController*> toolbar_actions =
442       toolbarActionsBar_->toolbar_actions();
443   for (NSUInteger i = 0; i < [buttons_ count]; ++i) {
444     if ([[buttons_ objectAtIndex:i] viewController] != toolbar_actions[i]) {
445       size_t j = i + 1;
446       while (toolbar_actions[i] != [[buttons_ objectAtIndex:j] viewController])
447         ++j;
448       [buttons_ exchangeObjectAtIndex:i withObjectAtIndex: j];
449     }
450   }
452   BOOL animate = !toolbarActionsBar_->suppress_animation();
453   [self showChevronIfNecessaryInFrame:[containerView_ frame] animate:animate];
454   for (NSUInteger i = 0; i < [buttons_ count]; ++i) {
455     if (![[buttons_ objectAtIndex:i] isBeingDragged])
456       [self moveButton:[buttons_ objectAtIndex:i]
457                toIndex:i
458                animate:animate];
459   }
462 - (void)removeViewForAction:(ToolbarActionViewController*)action {
463   BrowserActionButton* button = [self buttonForId:action->GetId()];
465   [button removeFromSuperview];
466   // It may or may not be hidden, but it won't matter to NSMutableArray either
467   // way.
468   [hiddenButtons_ removeObject:button];
470   [button onRemoved];
472   [buttons_ removeObject:button];
475 - (void)removeAllViews {
476   for (BrowserActionButton* button in buttons_.get()) {
477     [button removeFromSuperview];
478     [button onRemoved];
479     [hiddenButtons_ removeObject:button];
480   }
481   [buttons_ removeAllObjects];
484 - (void)resizeContainerToWidth:(CGFloat)width
485                  shouldAnimate:(BOOL)animate {
486   [self updateContainerVisibility];
487   [containerView_ setMaxWidth:
488       toolbarActionsBar_->IconCountToWidth([self buttonCount])];
489   [containerView_ resizeToWidth:width
490                         animate:animate];
491   NSRect frame = animate ? [containerView_ animationEndFrame] :
492                            [containerView_ frame];
494   [self showChevronIfNecessaryInFrame:frame animate:animate];
496   [containerView_ setNeedsDisplay:YES];
498   if (!animate) {
499     [[NSNotificationCenter defaultCenter]
500         postNotificationName:kBrowserActionVisibilityChangedNotification
501                       object:self];
502     [self redraw];
503   }
506 - (BOOL)updateContainerVisibility {
507   BOOL hidden = [buttons_ count] == 0;
508   if ([containerView_ isHidden] != hidden)
509     [containerView_ setHidden:hidden];
510   return !hidden;
513 - (void)updateButtonOpacity {
514   for (BrowserActionButton* button in buttons_.get()) {
515     NSRect buttonFrame = [button frame];
516     if (NSContainsRect([containerView_ bounds], buttonFrame)) {
517       if ([button alphaValue] != 1.0)
518         [button setAlphaValue:1.0];
520       continue;
521     }
522     CGFloat intersectionWidth =
523         NSWidth(NSIntersectionRect([containerView_ bounds], buttonFrame));
524     CGFloat alpha = std::max(static_cast<CGFloat>(0.0),
525                              intersectionWidth / NSWidth(buttonFrame));
526     [button setAlphaValue:alpha];
527     [button setNeedsDisplay:YES];
528   }
531 - (BrowserActionButton*)buttonForId:(const std::string&)id {
532   for (BrowserActionButton* button in buttons_.get()) {
533     if ([button viewController]->GetId() == id)
534       return button;
535   }
536   return nil;
539 - (void)containerFrameChanged:(NSNotification*)notification {
540   [self updateButtonOpacity];
541   [[containerView_ window] invalidateCursorRectsForView:containerView_];
542   [self updateChevronPositionInFrame:[containerView_ frame]];
545 - (void)containerDragStart:(NSNotification*)notification {
546   [self setChevronHidden:YES inFrame:[containerView_ frame] animate:YES];
547   while([hiddenButtons_ count] > 0) {
548     BrowserActionButton* button = [hiddenButtons_ objectAtIndex:0];
549     [button setAlphaValue:1.0];
550     [containerView_ addSubview:button];
551     [hiddenButtons_ removeObjectAtIndex:0];
552   }
555 - (void)containerDragging:(NSNotification*)notification {
556   [[NSNotificationCenter defaultCenter]
557       postNotificationName:kBrowserActionGrippyDraggingNotification
558                     object:self];
561 - (void)containerDragFinished:(NSNotification*)notification {
562   for (BrowserActionButton* button in buttons_.get()) {
563     NSRect buttonFrame = [button frame];
564     if (NSContainsRect([containerView_ bounds], buttonFrame))
565       continue;
567     CGFloat intersectionWidth =
568         NSWidth(NSIntersectionRect([containerView_ bounds], buttonFrame));
569     // Pad the threshold by 5 pixels in order to have the buttons hide more
570     // easily.
571     if (([containerView_ grippyPinned] && intersectionWidth > 0) ||
572         (intersectionWidth <= (NSWidth(buttonFrame) / 2) + 5.0)) {
573       [button setAlphaValue:0.0];
574       [button removeFromSuperview];
575       [hiddenButtons_ addObject:button];
576     }
577   }
578   [self updateGrippyCursors];
580   toolbarActionsBar_->OnResizeComplete(
581       toolbarActionsBar_->IconCountToWidth([self visibleButtonCount]));
583   [[NSNotificationCenter defaultCenter]
584       postNotificationName:kBrowserActionGrippyDragFinishedNotification
585                     object:self];
588 - (void)containerWillTranslateOnX:(NSNotification*)notification {
589   [[NSNotificationCenter defaultCenter]
590       postNotificationName:kBrowserActionGrippyWillDragNotification
591                     object:self
592                   userInfo:notification.userInfo];
595 - (void)actionButtonDragging:(NSNotification*)notification {
596   suppressChevron_ = YES;
597   if (![self chevronIsHidden])
598     [self setChevronHidden:YES inFrame:[containerView_ frame] animate:YES];
600   // Determine what index the dragged button should lie in, alter the model and
601   // reposition the buttons.
602   CGFloat dragThreshold = ToolbarActionsBar::IconWidth(false) / 2;
603   BrowserActionButton* draggedButton = [notification object];
604   NSRect draggedButtonFrame = [draggedButton frame];
606   NSUInteger index = 0;
607   std::vector<ToolbarActionViewController*> toolbar_actions =
608       toolbarActionsBar_->toolbar_actions();
609   for (ToolbarActionViewController* action : toolbar_actions) {
610     BrowserActionButton* button = [self buttonForId:(action->GetId())];
611     CGFloat intersectionWidth =
612         NSWidth(NSIntersectionRect(draggedButtonFrame, [button frame]));
614     if (intersectionWidth > dragThreshold && button != draggedButton &&
615         ![button isAnimating] && index < [self visibleButtonCount]) {
616       toolbarActionsBar_->OnDragDrop(
617           [buttons_ indexOfObject:draggedButton],
618           index,
619           ToolbarActionsBar::DRAG_TO_SAME);
620       return;
621     }
622     ++index;
623   }
626 - (void)actionButtonDragFinished:(NSNotification*)notification {
627   suppressChevron_ = NO;
628   [self redraw];
631 - (void)moveButton:(BrowserActionButton*)button
632            toIndex:(NSUInteger)index
633            animate:(BOOL)animate {
634   const ToolbarActionsBar::PlatformSettings& platformSettings =
635       toolbarActionsBar_->platform_settings();
636   CGFloat xOffset = platformSettings.left_padding +
637       (index * ToolbarActionsBar::IconWidth(true));
638   NSRect buttonFrame = [button frame];
639   buttonFrame.origin.x = xOffset;
640   [button setFrame:buttonFrame animate:animate];
642   if (index < toolbarActionsBar_->GetIconCount()) {
643     // Make sure the button is within the visible container.
644     if ([button superview] != containerView_) {
645       [containerView_ addSubview:button];
646       [button setAlphaValue:1.0];
647       [hiddenButtons_ removeObjectIdenticalTo:button];
648     }
649   } else if (![hiddenButtons_ containsObject:button]) {
650     [hiddenButtons_ addObject:button];
651     [button removeFromSuperview];
652     [button setAlphaValue:0.0];
653   }
656 - (BOOL)browserActionClicked:(BrowserActionButton*)button
657                  shouldGrant:(BOOL)shouldGrant {
658   return [button viewController]->ExecuteAction(shouldGrant);
661 - (BOOL)browserActionClicked:(BrowserActionButton*)button {
662   return [self browserActionClicked:button
663                         shouldGrant:YES];
666 - (void)showChevronIfNecessaryInFrame:(NSRect)frame animate:(BOOL)animate {
667   bool hidden = suppressChevron_ ||
668       toolbarActionsBar_->GetIconCount() == [self buttonCount];
669   [self setChevronHidden:hidden
670                  inFrame:frame
671                  animate:animate];
674 - (void)updateChevronPositionInFrame:(NSRect)frame {
675   CGFloat xPos = NSWidth(frame) - kChevronWidth;
676   NSRect buttonFrame = NSMakeRect(xPos,
677                                   kBrowserActionOriginYOffset,
678                                   kChevronWidth,
679                                   ToolbarActionsBar::IconHeight());
680   [chevronMenuButton_ setFrame:buttonFrame];
683 - (void)setChevronHidden:(BOOL)hidden
684                  inFrame:(NSRect)frame
685                  animate:(BOOL)animate {
686   if (hidden == [self chevronIsHidden])
687     return;
689   if (!chevronMenuButton_.get()) {
690     chevronMenuButton_.reset([[MenuButton alloc] init]);
691     [chevronMenuButton_ setOpenMenuOnClick:YES];
692     [chevronMenuButton_ setBordered:NO];
693     [chevronMenuButton_ setShowsBorderOnlyWhileMouseInside:YES];
695     [[chevronMenuButton_ cell] setImageID:IDR_BROWSER_ACTIONS_OVERFLOW
696                            forButtonState:image_button_cell::kDefaultState];
697     [[chevronMenuButton_ cell] setImageID:IDR_BROWSER_ACTIONS_OVERFLOW_H
698                            forButtonState:image_button_cell::kHoverState];
699     [[chevronMenuButton_ cell] setImageID:IDR_BROWSER_ACTIONS_OVERFLOW_P
700                            forButtonState:image_button_cell::kPressedState];
702     overflowMenu_.reset([[NSMenu alloc] initWithTitle:@""]);
703     [overflowMenu_ setAutoenablesItems:NO];
704     [overflowMenu_ setDelegate:self];
705     [chevronMenuButton_ setAttachedMenu:overflowMenu_];
707     [containerView_ addSubview:chevronMenuButton_];
708   }
710   [self updateChevronPositionInFrame:frame];
712   // Stop any running animation.
713   [chevronAnimation_ stopAnimation];
715   if (!animate) {
716     [chevronMenuButton_ setHidden:hidden];
717     return;
718   }
720   NSDictionary* animationDictionary;
721   if (hidden) {
722     animationDictionary = [NSDictionary dictionaryWithObjectsAndKeys:
723         chevronMenuButton_.get(), NSViewAnimationTargetKey,
724         NSViewAnimationFadeOutEffect, NSViewAnimationEffectKey,
725         nil];
726   } else {
727     [chevronMenuButton_ setHidden:NO];
728     animationDictionary = [NSDictionary dictionaryWithObjectsAndKeys:
729         chevronMenuButton_.get(), NSViewAnimationTargetKey,
730         NSViewAnimationFadeInEffect, NSViewAnimationEffectKey,
731         nil];
732   }
733   [chevronAnimation_ setViewAnimations:
734       [NSArray arrayWithObject:animationDictionary]];
735   [chevronAnimation_ startAnimation];
738 - (void)chevronItemSelected:(id)menuItem {
739   [self browserActionClicked:[menuItem representedObject]];
742 - (void)updateGrippyCursors {
743   [containerView_ setCanDragLeft:[hiddenButtons_ count] > 0];
744   [containerView_ setCanDragRight:[self visibleButtonCount] > 0];
745   [[containerView_ window] invalidateCursorRectsForView:containerView_];
748 - (ToolbarActionsBar*)toolbarActionsBar {
749   return toolbarActionsBar_.get();
752 #pragma mark -
753 #pragma mark Testing Methods
755 - (BrowserActionButton*)buttonWithIndex:(NSUInteger)index {
756   const std::vector<ToolbarActionViewController*>& toolbar_actions =
757       toolbarActionsBar_->toolbar_actions();
758   if (index < toolbar_actions.size())
759     return [self buttonForId:toolbar_actions[index]->GetId()];
760   return nil;
763 @end