Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / ui / cocoa / extensions / browser_actions_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/extensions/browser_actions_controller.h"
6
7 #include <cmath>
8 #include <string>
9
10 #include "base/strings/sys_string_conversions.h"
11 #include "chrome/browser/extensions/extension_action.h"
12 #include "chrome/browser/extensions/extension_action_manager.h"
13 #include "chrome/browser/extensions/extension_toolbar_model.h"
14 #include "chrome/browser/profiles/profile.h"
15 #include "chrome/browser/ui/browser.h"
16 #include "chrome/browser/ui/browser_window.h"
17 #import "chrome/browser/ui/cocoa/extensions/browser_action_button.h"
18 #import "chrome/browser/ui/cocoa/extensions/browser_actions_container_view.h"
19 #import "chrome/browser/ui/cocoa/extensions/extension_popup_controller.h"
20 #import "chrome/browser/ui/cocoa/image_button_cell.h"
21 #import "chrome/browser/ui/cocoa/menu_button.h"
22 #include "chrome/browser/ui/extensions/extension_action_view_controller.h"
23 #include "chrome/browser/ui/tabs/tab_strip_model.h"
24 #include "grit/theme_resources.h"
25 #import "third_party/google_toolbox_for_mac/src/AppKit/GTMNSAnimation+Duration.h"
26
27 using extensions::Extension;
28 using extensions::ExtensionList;
29
30 NSString* const kBrowserActionVisibilityChangedNotification =
31     @"BrowserActionVisibilityChangedNotification";
32
33 namespace {
34 const CGFloat kAnimationDuration = 0.2;
35
36 const CGFloat kChevronWidth = 18;
37
38 // Since the container is the maximum height of the toolbar, we have
39 // to move the buttons up by this amount in order to have them look
40 // vertically centered within the toolbar.
41 const CGFloat kBrowserActionOriginYOffset = 5.0;
42
43 // The size of each button on the toolbar.
44 const CGFloat kBrowserActionHeight = 29.0;
45 const CGFloat kBrowserActionWidth = 29.0;
46
47 // The padding between browser action buttons.
48 const CGFloat kBrowserActionButtonPadding = 2.0;
49
50 // Padding between Omnibox and first button.  Since the buttons have a
51 // pixel of internal padding, this needs an extra pixel.
52 const CGFloat kBrowserActionLeftPadding = kBrowserActionButtonPadding + 1.0;
53
54 // How far to inset from the bottom of the view to get the top border
55 // of the popup 2px below the bottom of the Omnibox.
56 const CGFloat kBrowserActionBubbleYOffset = 3.0;
57
58 }  // namespace
59
60 @interface BrowserActionsController(Private)
61 // Used during initialization to create the BrowserActionButton objects from the
62 // stored toolbar model.
63 - (void)createButtons;
64
65 // Creates and then adds the given extension's action button to the container
66 // at the given index within the container. It does not affect the toolbar model
67 // object since it is called when the toolbar model changes.
68 - (void)createActionButtonForExtension:(const Extension*)extension
69                              withIndex:(NSUInteger)index;
70
71 // Removes an action button for the given extension from the container. This
72 // method also does not affect the underlying toolbar model since it is called
73 // when the toolbar model changes.
74 - (void)removeActionButtonForExtension:(const Extension*)extension;
75
76 // Useful in the case of a Browser Action being added/removed from the middle of
77 // the container, this method repositions each button according to the current
78 // toolbar model.
79 - (void)positionActionButtonsAndAnimate:(BOOL)animate;
80
81 // During container resizing, buttons become more transparent as they are pushed
82 // off the screen. This method updates each button's opacity determined by the
83 // position of the button.
84 - (void)updateButtonOpacity;
85
86 // Returns the existing button associated with the given id; nil if it cannot be
87 // found.
88 - (BrowserActionButton*)buttonForId:(const std::string&)id;
89
90 // Returns the preferred width of the container given the number of visible
91 // buttons |buttonCount|.
92 - (CGFloat)containerWidthWithButtonCount:(NSUInteger)buttonCount;
93
94 // Returns the number of buttons that can fit in the container according to its
95 // current size.
96 - (NSUInteger)containerButtonCapacity;
97
98 // Notification handlers for events registered by the class.
99
100 // Updates each button's opacity, the cursor rects and chevron position.
101 - (void)containerFrameChanged:(NSNotification*)notification;
102
103 // Hides the chevron and unhides every hidden button so that dragging the
104 // container out smoothly shows the Browser Action buttons.
105 - (void)containerDragStart:(NSNotification*)notification;
106
107 // Sends a notification for the toolbar to reposition surrounding UI elements.
108 - (void)containerDragging:(NSNotification*)notification;
109
110 // Determines which buttons need to be hidden based on the new size, hides them
111 // and updates the chevron overflow menu. Also fires a notification to let the
112 // toolbar know that the drag has finished.
113 - (void)containerDragFinished:(NSNotification*)notification;
114
115 // Sends a notification for the toolbar to determine whether the container can
116 // translate with a delta on x-axis.
117 - (void)containerWillTranslateOnX:(NSNotification*)notification;
118
119 // Adjusts the position of the surrounding action buttons depending on where the
120 // button is within the container.
121 - (void)actionButtonDragging:(NSNotification*)notification;
122
123 // Updates the position of the Browser Actions within the container. This fires
124 // when _any_ Browser Action button is done dragging to keep all open windows in
125 // sync visually.
126 - (void)actionButtonDragFinished:(NSNotification*)notification;
127
128 // Moves the given button both visually and within the toolbar model to the
129 // specified index.
130 - (void)moveButton:(BrowserActionButton*)button
131            toIndex:(NSUInteger)index
132            animate:(BOOL)animate;
133
134 // Handles when the given BrowserActionButton object is clicked and whether
135 // it should grant tab permissions. API-simulated clicks should not grant.
136 - (BOOL)browserActionClicked:(BrowserActionButton*)button
137                  shouldGrant:(BOOL)shouldGrant;
138 - (BOOL)browserActionClicked:(BrowserActionButton*)button;
139
140 // The reason |frame| is specified in these chevron functions is because the
141 // container may be animating and the end frame of the animation should be
142 // passed instead of the current frame (which may be off and cause the chevron
143 // to jump at the end of its animation).
144
145 // Shows the overflow chevron button depending on whether there are any hidden
146 // extensions within the frame given.
147 - (void)showChevronIfNecessaryInFrame:(NSRect)frame animate:(BOOL)animate;
148
149 // Moves the chevron to its correct position within |frame|.
150 - (void)updateChevronPositionInFrame:(NSRect)frame;
151
152 // Shows or hides the chevron, animating as specified by |animate|.
153 - (void)setChevronHidden:(BOOL)hidden
154                  inFrame:(NSRect)frame
155                  animate:(BOOL)animate;
156
157 // Handles when a menu item within the chevron overflow menu is selected.
158 - (void)chevronItemSelected:(id)menuItem;
159
160 // Updates the container's grippy cursor based on the number of hidden buttons.
161 - (void)updateGrippyCursors;
162 @end
163
164 // A helper class to proxy extension notifications to the view controller's
165 // appropriate methods.
166 class ExtensionServiceObserverBridge
167     : public extensions::ExtensionToolbarModel::Observer {
168  public:
169   ExtensionServiceObserverBridge(BrowserActionsController* owner,
170                                  Browser* browser)
171     : owner_(owner), browser_(browser) {
172   }
173
174   // extensions::ExtensionToolbarModel::Observer implementation.
175   void ToolbarExtensionAdded(const Extension* extension, int index) override {
176     [owner_ createActionButtonForExtension:extension withIndex:index];
177     [owner_ resizeContainerAndAnimate:NO];
178   }
179
180   void ToolbarExtensionRemoved(const Extension* extension) override {
181     [owner_ removeActionButtonForExtension:extension];
182     [owner_ resizeContainerAndAnimate:NO];
183   }
184
185   void ToolbarExtensionMoved(const Extension* extension, int index) override {}
186
187   void ToolbarExtensionUpdated(const Extension* extension) override {
188     BrowserActionButton* button = [owner_ buttonForId:extension->id()];
189     if (button)
190       [button updateState];
191   }
192
193   bool ShowExtensionActionPopup(const Extension* extension,
194                                 bool grant_active_tab) override {
195     // Do not override other popups and only show in active window.
196     ExtensionPopupController* popup = [ExtensionPopupController popup];
197     if (popup || !browser_->window()->IsActive())
198       return false;
199
200     BrowserActionButton* button = [owner_ buttonForId:extension->id()];
201     return button && [button viewController]->ExecuteAction(grant_active_tab);
202   }
203
204   void ToolbarVisibleCountChanged() override {}
205
206   void OnToolbarReorderNecessary(content::WebContents* web_contents) override {
207     // TODO(devlin): Implement on mac.
208   }
209
210   void ToolbarHighlightModeChanged(bool is_highlighting) override {}
211
212   Browser* GetBrowser() override { return browser_; }
213
214  private:
215   // The object we need to inform when we get a notification. Weak. Owns us.
216   BrowserActionsController* owner_;
217
218   // The browser we listen for events from. Weak.
219   Browser* browser_;
220
221   DISALLOW_COPY_AND_ASSIGN(ExtensionServiceObserverBridge);
222 };
223
224 @implementation BrowserActionsController
225
226 @synthesize containerView = containerView_;
227
228 #pragma mark -
229 #pragma mark Public Methods
230
231 - (id)initWithBrowser:(Browser*)browser
232         containerView:(BrowserActionsContainerView*)container {
233   DCHECK(browser && container);
234
235   if ((self = [super init])) {
236     browser_ = browser;
237     profile_ = browser->profile();
238
239     observer_.reset(new ExtensionServiceObserverBridge(self, browser_));
240     toolbarModel_ = extensions::ExtensionToolbarModel::Get(profile_);
241     if (toolbarModel_)
242       toolbarModel_->AddObserver(observer_.get());
243
244     containerView_ = container;
245     [containerView_ setPostsFrameChangedNotifications:YES];
246     [[NSNotificationCenter defaultCenter]
247         addObserver:self
248            selector:@selector(containerFrameChanged:)
249                name:NSViewFrameDidChangeNotification
250              object:containerView_];
251     [[NSNotificationCenter defaultCenter]
252         addObserver:self
253            selector:@selector(containerDragStart:)
254                name:kBrowserActionGrippyDragStartedNotification
255              object:containerView_];
256     [[NSNotificationCenter defaultCenter]
257         addObserver:self
258            selector:@selector(containerDragging:)
259                name:kBrowserActionGrippyDraggingNotification
260              object:containerView_];
261     [[NSNotificationCenter defaultCenter]
262         addObserver:self
263            selector:@selector(containerDragFinished:)
264                name:kBrowserActionGrippyDragFinishedNotification
265              object:containerView_];
266     [[NSNotificationCenter defaultCenter]
267         addObserver:self
268            selector:@selector(containerWillTranslateOnX:)
269                name:kBrowserActionGrippyWillDragNotification
270              object:containerView_];
271     // Listen for a finished drag from any button to make sure each open window
272     // stays in sync.
273     [[NSNotificationCenter defaultCenter]
274       addObserver:self
275          selector:@selector(actionButtonDragFinished:)
276              name:kBrowserActionButtonDragEndNotification
277            object:nil];
278
279     chevronAnimation_.reset([[NSViewAnimation alloc] init]);
280     [chevronAnimation_ gtm_setDuration:kAnimationDuration
281                              eventMask:NSLeftMouseUpMask];
282     [chevronAnimation_ setAnimationBlockingMode:NSAnimationNonblocking];
283
284     hiddenButtons_.reset([[NSMutableArray alloc] init]);
285     buttons_.reset([[NSMutableDictionary alloc] init]);
286     [self createButtons];
287     [self showChevronIfNecessaryInFrame:[containerView_ frame] animate:NO];
288     [self updateGrippyCursors];
289     [container setResizable:!profile_->IsOffTheRecord()];
290   }
291
292   return self;
293 }
294
295 - (void)dealloc {
296   if (toolbarModel_)
297     toolbarModel_->RemoveObserver(observer_.get());
298
299   [[NSNotificationCenter defaultCenter] removeObserver:self];
300   [super dealloc];
301 }
302
303 - (void)update {
304   for (BrowserActionButton* button in [buttons_ allValues])
305     [button updateState];
306 }
307
308 - (NSUInteger)buttonCount {
309   return [buttons_ count];
310 }
311
312 - (NSUInteger)visibleButtonCount {
313   return [self buttonCount] - [hiddenButtons_ count];
314 }
315
316 - (void)resizeContainerAndAnimate:(BOOL)animate {
317   int iconCount = toolbarModel_->visible_icon_count();
318
319   [containerView_ resizeToWidth:[self containerWidthWithButtonCount:iconCount]
320                         animate:animate];
321   NSRect frame = animate ? [containerView_ animationEndFrame] :
322                            [containerView_ frame];
323
324   [self showChevronIfNecessaryInFrame:frame animate:animate];
325
326   if (!animate) {
327     [[NSNotificationCenter defaultCenter]
328         postNotificationName:kBrowserActionVisibilityChangedNotification
329                       object:self];
330   }
331 }
332
333 - (CGFloat)savedWidth {
334   if (!toolbarModel_)
335     return 0;
336
337   int savedButtonCount = toolbarModel_->visible_icon_count();
338   if (static_cast<NSUInteger>(savedButtonCount) > [self buttonCount])
339     savedButtonCount = [self buttonCount];
340   return [self containerWidthWithButtonCount:savedButtonCount];
341 }
342
343 - (NSPoint)popupPointForId:(const std::string&)id {
344   NSButton* button = [self buttonForId:id];
345   if (!button)
346     return NSZeroPoint;
347
348   if ([hiddenButtons_ containsObject:button])
349     button = chevronMenuButton_.get();
350
351   // Anchor point just above the center of the bottom.
352   const NSRect bounds = [button bounds];
353   DCHECK([button isFlipped]);
354   NSPoint anchor = NSMakePoint(NSMidX(bounds),
355                                NSMaxY(bounds) - kBrowserActionBubbleYOffset);
356   return [button convertPoint:anchor toView:nil];
357 }
358
359 - (BOOL)chevronIsHidden {
360   if (!chevronMenuButton_.get())
361     return YES;
362
363   if (![chevronAnimation_ isAnimating])
364     return [chevronMenuButton_ isHidden];
365
366   DCHECK([[chevronAnimation_ viewAnimations] count] > 0);
367
368   // The chevron is animating in or out. Determine which one and have the return
369   // value reflect where the animation is headed.
370   NSString* effect = [[[chevronAnimation_ viewAnimations] objectAtIndex:0]
371       valueForKey:NSViewAnimationEffectKey];
372   if (effect == NSViewAnimationFadeInEffect) {
373     return NO;
374   } else if (effect == NSViewAnimationFadeOutEffect) {
375     return YES;
376   }
377
378   NOTREACHED();
379   return YES;
380 }
381
382 - (void)activateBrowserAction:(const std::string&)id {
383   BrowserActionButton* button = [self buttonForId:id];
384   // |button| can be nil when the browser action has its button hidden.
385   if (button)
386     [self browserActionClicked:button];
387 }
388
389 - (content::WebContents*)currentWebContents {
390   return browser_->tab_strip_model()->GetActiveWebContents();
391 }
392
393 #pragma mark -
394 #pragma mark NSMenuDelegate
395
396 - (void)menuNeedsUpdate:(NSMenu*)menu {
397   [menu removeAllItems];
398
399   // See menu_button.h for documentation on why this is needed.
400   [menu addItemWithTitle:@"" action:nil keyEquivalent:@""];
401
402   for (BrowserActionButton* button in hiddenButtons_.get()) {
403     NSString* name =
404         base::SysUTF16ToNSString([button viewController]->GetActionName());
405     NSMenuItem* item =
406         [menu addItemWithTitle:name
407                         action:@selector(chevronItemSelected:)
408                  keyEquivalent:@""];
409     [item setRepresentedObject:button];
410     [item setImage:[button compositedImage]];
411     [item setTarget:self];
412     [item setEnabled:[button isEnabled]];
413   }
414 }
415
416 #pragma mark -
417 #pragma mark Private Methods
418
419 - (void)createButtons {
420   if (!toolbarModel_)
421     return;
422
423   NSUInteger i = 0;
424   for (ExtensionList::const_iterator iter =
425            toolbarModel_->toolbar_items().begin();
426        iter != toolbarModel_->toolbar_items().end(); ++iter) {
427     [self createActionButtonForExtension:iter->get() withIndex:i++];
428   }
429
430   CGFloat width = [self savedWidth];
431   [containerView_ resizeToWidth:width animate:NO];
432 }
433
434 - (void)createActionButtonForExtension:(const Extension*)extension
435                              withIndex:(NSUInteger)index {
436   // Show the container if it's the first button. Otherwise it will be shown
437   // already.
438   if ([self buttonCount] == 0)
439     [containerView_ setHidden:NO];
440
441   NSRect buttonFrame = NSMakeRect(0.0, kBrowserActionOriginYOffset,
442                                   kBrowserActionWidth, kBrowserActionHeight);
443   ExtensionAction* extensionAction =
444       extensions::ExtensionActionManager::Get(browser_->profile())->
445           GetExtensionAction(*extension);
446   DCHECK(extensionAction)
447         << "Don't create a BrowserActionButton if there is no browser action.";
448   scoped_ptr<ToolbarActionViewController> viewController(
449       new ExtensionActionViewController(extension, browser_, extensionAction));
450   BrowserActionButton* newButton =
451       [[[BrowserActionButton alloc]
452          initWithFrame:buttonFrame
453         viewController:viewController.Pass()
454             controller:self] autorelease];
455   [newButton setTarget:self];
456   [newButton setAction:@selector(browserActionClicked:)];
457   NSString* buttonKey = base::SysUTF8ToNSString(extension->id());
458   if (!buttonKey)
459     return;
460   [buttons_ setObject:newButton forKey:buttonKey];
461
462   [self positionActionButtonsAndAnimate:NO];
463
464   [[NSNotificationCenter defaultCenter]
465       addObserver:self
466          selector:@selector(actionButtonDragging:)
467              name:kBrowserActionButtonDraggingNotification
468            object:newButton];
469
470
471   [containerView_ setMaxWidth:
472       [self containerWidthWithButtonCount:[self buttonCount]]];
473   [containerView_ setNeedsDisplay:YES];
474 }
475
476 - (void)removeActionButtonForExtension:(const Extension*)extension {
477   NSString* buttonKey = base::SysUTF8ToNSString(extension->id());
478   if (!buttonKey)
479     return;
480
481   BrowserActionButton* button = [buttons_ objectForKey:buttonKey];
482
483   [button removeFromSuperview];
484   // It may or may not be hidden, but it won't matter to NSMutableArray either
485   // way.
486   [hiddenButtons_ removeObject:button];
487
488   [buttons_ removeObjectForKey:buttonKey];
489   if ([self buttonCount] == 0) {
490     // No more buttons? Hide the container.
491     [containerView_ setHidden:YES];
492   } else {
493     [self positionActionButtonsAndAnimate:NO];
494   }
495   [containerView_ setMaxWidth:
496       [self containerWidthWithButtonCount:[self buttonCount]]];
497   [containerView_ setNeedsDisplay:YES];
498 }
499
500 - (void)positionActionButtonsAndAnimate:(BOOL)animate {
501   NSUInteger i = 0;
502   for (ExtensionList::const_iterator iter =
503            toolbarModel_->toolbar_items().begin();
504        iter != toolbarModel_->toolbar_items().end(); ++iter) {
505     BrowserActionButton* button = [self buttonForId:(iter->get()->id())];
506     if (!button)
507       continue;
508     if (![button isBeingDragged])
509       [self moveButton:button toIndex:i animate:animate];
510     ++i;
511   }
512 }
513
514 - (void)updateButtonOpacity {
515   for (BrowserActionButton* button in [buttons_ allValues]) {
516     NSRect buttonFrame = [button frame];
517     if (NSContainsRect([containerView_ bounds], buttonFrame)) {
518       if ([button alphaValue] != 1.0)
519         [button setAlphaValue:1.0];
520
521       continue;
522     }
523     CGFloat intersectionWidth =
524         NSWidth(NSIntersectionRect([containerView_ bounds], buttonFrame));
525     CGFloat alpha = std::max(static_cast<CGFloat>(0.0),
526                              intersectionWidth / NSWidth(buttonFrame));
527     [button setAlphaValue:alpha];
528     [button setNeedsDisplay:YES];
529   }
530 }
531
532 - (BrowserActionButton*)buttonForId:(const std::string&)id {
533   NSString* nsId = base::SysUTF8ToNSString(id);
534   DCHECK(nsId);
535   if (!nsId)
536     return nil;
537   return [buttons_ objectForKey:nsId];
538 }
539
540 - (CGFloat)containerWidthWithButtonCount:(NSUInteger)buttonCount {
541   // Left-side padding which works regardless of whether a button or
542   // chevron leads.
543   CGFloat width = kBrowserActionLeftPadding;
544
545   // Include the buttons and padding between.
546   if (buttonCount > 0) {
547     width += buttonCount * kBrowserActionWidth;
548     width += (buttonCount - 1) * kBrowserActionButtonPadding;
549   }
550
551   // Make room for the chevron if any buttons are hidden.
552   if ([self buttonCount] != [self visibleButtonCount]) {
553     // Chevron and buttons both include 1px padding w/in their bounds,
554     // so this leaves 2px between the last browser action and chevron,
555     // and also works right if the chevron is the only button.
556     width += kChevronWidth;
557   }
558
559   return width;
560 }
561
562 - (NSUInteger)containerButtonCapacity {
563   // Edge-to-edge span of the browser action buttons.
564   CGFloat actionSpan = [self savedWidth] - kBrowserActionLeftPadding;
565
566   // Add in some padding for the browser action on the end, then
567   // divide out to get the number of action buttons that fit.
568   return (actionSpan + kBrowserActionButtonPadding) /
569       (kBrowserActionWidth + kBrowserActionButtonPadding);
570 }
571
572 - (void)containerFrameChanged:(NSNotification*)notification {
573   [self updateButtonOpacity];
574   [[containerView_ window] invalidateCursorRectsForView:containerView_];
575   [self updateChevronPositionInFrame:[containerView_ frame]];
576 }
577
578 - (void)containerDragStart:(NSNotification*)notification {
579   [self setChevronHidden:YES inFrame:[containerView_ frame] animate:YES];
580   while([hiddenButtons_ count] > 0) {
581     BrowserActionButton* button = [hiddenButtons_ objectAtIndex:0];
582     [button setAlphaValue:1.0];
583     [containerView_ addSubview:button];
584     [hiddenButtons_ removeObjectAtIndex:0];
585   }
586 }
587
588 - (void)containerDragging:(NSNotification*)notification {
589   [[NSNotificationCenter defaultCenter]
590       postNotificationName:kBrowserActionGrippyDraggingNotification
591                     object:self];
592 }
593
594 - (void)containerDragFinished:(NSNotification*)notification {
595   for (ExtensionList::const_iterator iter =
596            toolbarModel_->toolbar_items().begin();
597        iter != toolbarModel_->toolbar_items().end(); ++iter) {
598     BrowserActionButton* button = [self buttonForId:(iter->get()->id())];
599     NSRect buttonFrame = [button frame];
600     if (NSContainsRect([containerView_ bounds], buttonFrame))
601       continue;
602
603     CGFloat intersectionWidth =
604         NSWidth(NSIntersectionRect([containerView_ bounds], buttonFrame));
605     // Pad the threshold by 5 pixels in order to have the buttons hide more
606     // easily.
607     if (([containerView_ grippyPinned] && intersectionWidth > 0) ||
608         (intersectionWidth <= (NSWidth(buttonFrame) / 2) + 5.0)) {
609       [button setAlphaValue:0.0];
610       [button removeFromSuperview];
611       [hiddenButtons_ addObject:button];
612     }
613   }
614   [self updateGrippyCursors];
615
616   toolbarModel_->SetVisibleIconCount([self visibleButtonCount]);
617
618   [[NSNotificationCenter defaultCenter]
619       postNotificationName:kBrowserActionGrippyDragFinishedNotification
620                     object:self];
621 }
622
623 - (void)containerWillTranslateOnX:(NSNotification*)notification {
624   [[NSNotificationCenter defaultCenter]
625       postNotificationName:kBrowserActionGrippyWillDragNotification
626                     object:self
627                   userInfo:notification.userInfo];
628 }
629
630 - (void)actionButtonDragging:(NSNotification*)notification {
631   if (![self chevronIsHidden])
632     [self setChevronHidden:YES inFrame:[containerView_ frame] animate:YES];
633
634   // Determine what index the dragged button should lie in, alter the model and
635   // reposition the buttons.
636   CGFloat dragThreshold = std::floor(kBrowserActionWidth / 2);
637   BrowserActionButton* draggedButton = [notification object];
638   NSRect draggedButtonFrame = [draggedButton frame];
639
640   NSUInteger index = 0;
641   for (ExtensionList::const_iterator iter =
642            toolbarModel_->toolbar_items().begin();
643        iter != toolbarModel_->toolbar_items().end(); ++iter) {
644     BrowserActionButton* button = [self buttonForId:(iter->get()->id())];
645     CGFloat intersectionWidth =
646         NSWidth(NSIntersectionRect(draggedButtonFrame, [button frame]));
647
648     if (intersectionWidth > dragThreshold && button != draggedButton &&
649         ![button isAnimating] && index < [self visibleButtonCount]) {
650       toolbarModel_->MoveExtensionIcon([draggedButton viewController]->GetId(),
651                                        index);
652       [self positionActionButtonsAndAnimate:YES];
653       return;
654     }
655     ++index;
656   }
657 }
658
659 - (void)actionButtonDragFinished:(NSNotification*)notification {
660   [self showChevronIfNecessaryInFrame:[containerView_ frame] animate:YES];
661   [self positionActionButtonsAndAnimate:YES];
662 }
663
664 - (void)moveButton:(BrowserActionButton*)button
665            toIndex:(NSUInteger)index
666            animate:(BOOL)animate {
667   CGFloat xOffset = kBrowserActionLeftPadding +
668       (index * (kBrowserActionWidth + kBrowserActionButtonPadding));
669   NSRect buttonFrame = [button frame];
670   buttonFrame.origin.x = xOffset;
671   [button setFrame:buttonFrame animate:animate];
672
673   if (index < [self containerButtonCapacity]) {
674     // Make sure the button is within the visible container.
675     if ([button superview] != containerView_) {
676       [containerView_ addSubview:button];
677       [button setAlphaValue:1.0];
678       [hiddenButtons_ removeObjectIdenticalTo:button];
679     }
680   } else if (![hiddenButtons_ containsObject:button]) {
681     [hiddenButtons_ addObject:button];
682     [button removeFromSuperview];
683     [button setAlphaValue:0.0];
684   }
685 }
686
687 - (BOOL)browserActionClicked:(BrowserActionButton*)button
688                  shouldGrant:(BOOL)shouldGrant {
689   return [button viewController]->ExecuteAction(shouldGrant);
690 }
691
692 - (BOOL)browserActionClicked:(BrowserActionButton*)button {
693   return [self browserActionClicked:button
694                         shouldGrant:YES];
695 }
696
697 - (void)showChevronIfNecessaryInFrame:(NSRect)frame animate:(BOOL)animate {
698   [self setChevronHidden:([self buttonCount] == [self visibleButtonCount])
699                  inFrame:frame
700                  animate:animate];
701 }
702
703 - (void)updateChevronPositionInFrame:(NSRect)frame {
704   CGFloat xPos = NSWidth(frame) - kChevronWidth;
705   NSRect buttonFrame = NSMakeRect(xPos,
706                                   kBrowserActionOriginYOffset,
707                                   kChevronWidth,
708                                   kBrowserActionHeight);
709   [chevronMenuButton_ setFrame:buttonFrame];
710 }
711
712 - (void)setChevronHidden:(BOOL)hidden
713                  inFrame:(NSRect)frame
714                  animate:(BOOL)animate {
715   if (hidden == [self chevronIsHidden])
716     return;
717
718   if (!chevronMenuButton_.get()) {
719     chevronMenuButton_.reset([[MenuButton alloc] init]);
720     [chevronMenuButton_ setOpenMenuOnClick:YES];
721     [chevronMenuButton_ setBordered:NO];
722     [chevronMenuButton_ setShowsBorderOnlyWhileMouseInside:YES];
723
724     [[chevronMenuButton_ cell] setImageID:IDR_BROWSER_ACTIONS_OVERFLOW
725                            forButtonState:image_button_cell::kDefaultState];
726     [[chevronMenuButton_ cell] setImageID:IDR_BROWSER_ACTIONS_OVERFLOW_H
727                            forButtonState:image_button_cell::kHoverState];
728     [[chevronMenuButton_ cell] setImageID:IDR_BROWSER_ACTIONS_OVERFLOW_P
729                            forButtonState:image_button_cell::kPressedState];
730
731     overflowMenu_.reset([[NSMenu alloc] initWithTitle:@""]);
732     [overflowMenu_ setAutoenablesItems:NO];
733     [overflowMenu_ setDelegate:self];
734     [chevronMenuButton_ setAttachedMenu:overflowMenu_];
735
736     [containerView_ addSubview:chevronMenuButton_];
737   }
738
739   [self updateChevronPositionInFrame:frame];
740
741   // Stop any running animation.
742   [chevronAnimation_ stopAnimation];
743
744   if (!animate) {
745     [chevronMenuButton_ setHidden:hidden];
746     return;
747   }
748
749   NSDictionary* animationDictionary;
750   if (hidden) {
751     animationDictionary = [NSDictionary dictionaryWithObjectsAndKeys:
752         chevronMenuButton_.get(), NSViewAnimationTargetKey,
753         NSViewAnimationFadeOutEffect, NSViewAnimationEffectKey,
754         nil];
755   } else {
756     [chevronMenuButton_ setHidden:NO];
757     animationDictionary = [NSDictionary dictionaryWithObjectsAndKeys:
758         chevronMenuButton_.get(), NSViewAnimationTargetKey,
759         NSViewAnimationFadeInEffect, NSViewAnimationEffectKey,
760         nil];
761   }
762   [chevronAnimation_ setViewAnimations:
763       [NSArray arrayWithObject:animationDictionary]];
764   [chevronAnimation_ startAnimation];
765 }
766
767 - (void)chevronItemSelected:(id)menuItem {
768   [self browserActionClicked:[menuItem representedObject]];
769 }
770
771 - (void)updateGrippyCursors {
772   [containerView_ setCanDragLeft:[hiddenButtons_ count] > 0];
773   [containerView_ setCanDragRight:[self visibleButtonCount] > 0];
774   [[containerView_ window] invalidateCursorRectsForView:containerView_];
775 }
776
777 #pragma mark -
778 #pragma mark Testing Methods
779
780 - (BrowserActionButton*)buttonWithIndex:(NSUInteger)index {
781   const extensions::ExtensionList& toolbar_items =
782       toolbarModel_->toolbar_items();
783   if (index < toolbar_items.size()) {
784     const Extension* extension = toolbar_items[index].get();
785     return [buttons_ objectForKey:base::SysUTF8ToNSString(extension->id())];
786   }
787   return nil;
788 }
789
790 @end