Home | History | Annotate | Download | only in bookmarks
      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/bookmarks/bookmark_bar_controller.h"
      6 
      7 #include "base/mac/bundle_locations.h"
      8 #include "base/mac/mac_util.h"
      9 #include "base/metrics/histogram.h"
     10 #include "base/prefs/pref_service.h"
     11 #include "base/strings/sys_string_conversions.h"
     12 #include "chrome/browser/bookmarks/bookmark_model.h"
     13 #include "chrome/browser/bookmarks/bookmark_model_factory.h"
     14 #include "chrome/browser/bookmarks/bookmark_utils.h"
     15 #include "chrome/browser/extensions/extension_service.h"
     16 #include "chrome/browser/prefs/incognito_mode_prefs.h"
     17 #include "chrome/browser/profiles/profile.h"
     18 #include "chrome/browser/themes/theme_properties.h"
     19 #include "chrome/browser/themes/theme_service.h"
     20 #import "chrome/browser/themes/theme_service_factory.h"
     21 #include "chrome/browser/ui/bookmarks/bookmark_editor.h"
     22 #include "chrome/browser/ui/bookmarks/bookmark_utils.h"
     23 #include "chrome/browser/ui/browser.h"
     24 #include "chrome/browser/ui/browser_list.h"
     25 #include "chrome/browser/ui/chrome_pages.h"
     26 #import "chrome/browser/ui/cocoa/background_gradient_view.h"
     27 #import "chrome/browser/ui/cocoa/bookmarks/bookmark_bar_bridge.h"
     28 #import "chrome/browser/ui/cocoa/bookmarks/bookmark_bar_folder_controller.h"
     29 #import "chrome/browser/ui/cocoa/bookmarks/bookmark_bar_folder_window.h"
     30 #import "chrome/browser/ui/cocoa/bookmarks/bookmark_bar_toolbar_view.h"
     31 #import "chrome/browser/ui/cocoa/bookmarks/bookmark_bar_view.h"
     32 #import "chrome/browser/ui/cocoa/bookmarks/bookmark_button.h"
     33 #import "chrome/browser/ui/cocoa/bookmarks/bookmark_button_cell.h"
     34 #import "chrome/browser/ui/cocoa/bookmarks/bookmark_context_menu_cocoa_controller.h"
     35 #import "chrome/browser/ui/cocoa/bookmarks/bookmark_editor_controller.h"
     36 #import "chrome/browser/ui/cocoa/bookmarks/bookmark_folder_target.h"
     37 #import "chrome/browser/ui/cocoa/bookmarks/bookmark_menu_cocoa_controller.h"
     38 #import "chrome/browser/ui/cocoa/bookmarks/bookmark_name_folder_controller.h"
     39 #import "chrome/browser/ui/cocoa/browser_window_controller.h"
     40 #import "chrome/browser/ui/cocoa/menu_button.h"
     41 #import "chrome/browser/ui/cocoa/presentation_mode_controller.h"
     42 #import "chrome/browser/ui/cocoa/themed_window.h"
     43 #import "chrome/browser/ui/cocoa/toolbar/toolbar_controller.h"
     44 #import "chrome/browser/ui/cocoa/view_id_util.h"
     45 #import "chrome/browser/ui/cocoa/view_resizer.h"
     46 #include "chrome/browser/ui/tabs/tab_strip_model.h"
     47 #include "chrome/browser/ui/webui/ntp/core_app_launcher_handler.h"
     48 #include "chrome/common/extensions/extension_constants.h"
     49 #include "chrome/common/pref_names.h"
     50 #include "chrome/common/url_constants.h"
     51 #include "content/public/browser/user_metrics.h"
     52 #include "content/public/browser/web_contents.h"
     53 #include "content/public/browser/web_contents_view.h"
     54 #include "grit/generated_resources.h"
     55 #include "grit/theme_resources.h"
     56 #include "grit/ui_resources.h"
     57 #import "ui/base/cocoa/cocoa_event_utils.h"
     58 #include "ui/base/l10n/l10n_util_mac.h"
     59 #include "ui/base/resource/resource_bundle.h"
     60 #include "ui/gfx/image/image.h"
     61 
     62 using content::OpenURLParams;
     63 using content::Referrer;
     64 using content::UserMetricsAction;
     65 using content::WebContents;
     66 
     67 // Bookmark bar state changing and animations
     68 //
     69 // The bookmark bar has three real states: "showing" (a normal bar attached to
     70 // the toolbar), "hidden", and "detached" (pretending to be part of the web
     71 // content on the NTP). It can, or at least should be able to, animate between
     72 // these states. There are several complications even without animation:
     73 //  - The placement of the bookmark bar is done by the BWC, and it needs to know
     74 //    the state in order to place the bookmark bar correctly (immediately below
     75 //    the toolbar when showing, below the infobar when detached).
     76 //  - The "divider" (a black line) needs to be drawn by either the toolbar (when
     77 //    the bookmark bar is hidden or detached) or by the bookmark bar (when it is
     78 //    showing). It should not be drawn by both.
     79 //  - The toolbar needs to vertically "compress" when the bookmark bar is
     80 //    showing. This ensures the proper display of both the bookmark bar and the
     81 //    toolbar, and gives a padded area around the bookmark bar items for right
     82 //    clicks, etc.
     83 //
     84 // Our model is that the BWC controls us and also the toolbar. We try not to
     85 // talk to the browser nor the toolbar directly, instead centralizing control in
     86 // the BWC. The key method by which the BWC controls us is
     87 // |-updateState:ChangeType:|. This invokes state changes, and at appropriate
     88 // times we request that the BWC do things for us via either the resize delegate
     89 // or our general delegate. If the BWC needs any information about what it
     90 // should do, or tell the toolbar to do, it can then query us back (e.g.,
     91 // |-isShownAs...|, |-getDesiredToolbarHeightCompression|,
     92 // |-toolbarDividerOpacity|, etc.).
     93 //
     94 // Animation-related complications:
     95 //  - Compression of the toolbar is touchy during animation. It must not be
     96 //    compressed while the bookmark bar is animating to/from showing (from/to
     97 //    hidden), otherwise it would look like the bookmark bar's contents are
     98 //    sliding out of the controls inside the toolbar. As such, we have to make
     99 //    sure that the bookmark bar is shown at the right location and at the
    100 //    right height (at various points in time).
    101 //  - Showing the divider is also complicated during animation between hidden
    102 //    and showing. We have to make sure that the toolbar does not show the
    103 //    divider despite the fact that it's not compressed. The exception to this
    104 //    is at the beginning/end of the animation when the toolbar is still
    105 //    uncompressed but the bookmark bar has height 0. If we're not careful, we
    106 //    get a flicker at this point.
    107 //  - We have to ensure that we do the right thing if we're told to change state
    108 //    while we're running an animation. The generic/easy thing to do is to jump
    109 //    to the end state of our current animation, and (if the new state change
    110 //    again involves an animation) begin the new animation. We can do better
    111 //    than that, however, and sometimes just change the current animation to go
    112 //    to the new end state (e.g., by "reversing" the animation in the showing ->
    113 //    hidden -> showing case). We also have to ensure that demands to
    114 //    immediately change state are always honoured.
    115 //
    116 // Pointers to animation logic:
    117 //  - |-moveToState:withAnimation:| starts animations, deciding which ones we
    118 //    know how to handle.
    119 //  - |-doBookmarkBarAnimation| has most of the actual logic.
    120 //  - |-getDesiredToolbarHeightCompression| and |-toolbarDividerOpacity| contain
    121 //    related logic.
    122 //  - The BWC's |-layoutSubviews| needs to know how to position things.
    123 //  - The BWC should implement |-bookmarkBar:didChangeFromState:toState:| and
    124 //    |-bookmarkBar:willAnimateFromState:toState:| in order to inform the
    125 //    toolbar of required changes.
    126 
    127 namespace {
    128 
    129 // Duration of the bookmark bar animations.
    130 const NSTimeInterval kBookmarkBarAnimationDuration = 0.12;
    131 
    132 void RecordAppLaunch(Profile* profile, GURL url) {
    133   DCHECK(profile->GetExtensionService());
    134   const extensions::Extension* extension =
    135       profile->GetExtensionService()->GetInstalledApp(url);
    136   if (!extension)
    137     return;
    138 
    139   CoreAppLauncherHandler::RecordAppLaunchType(
    140       extension_misc::APP_LAUNCH_BOOKMARK_BAR,
    141       extension->GetType());
    142 }
    143 
    144 }  // namespace
    145 
    146 @interface BookmarkBarController(Private)
    147 
    148 // Moves to the given next state (from the current state), possibly animating.
    149 // If |animate| is NO, it will stop any running animation and jump to the given
    150 // state. If YES, it may either (depending on implementation) jump to the end of
    151 // the current animation and begin the next one, or stop the current animation
    152 // mid-flight and animate to the next state.
    153 - (void)moveToState:(BookmarkBar::State)nextState
    154       withAnimation:(BOOL)animate;
    155 
    156 // Return the backdrop to the bookmark bar as various types.
    157 - (BackgroundGradientView*)backgroundGradientView;
    158 - (AnimatableView*)animatableView;
    159 
    160 // Create buttons for all items in the given bookmark node tree.
    161 // Modifies self->buttons_.  Do not add more buttons than will fit on the view.
    162 - (void)addNodesToButtonList:(const BookmarkNode*)node;
    163 
    164 // Create an autoreleased button appropriate for insertion into the bookmark
    165 // bar. Update |xOffset| with the offset appropriate for the subsequent button.
    166 - (BookmarkButton*)buttonForNode:(const BookmarkNode*)node
    167                          xOffset:(int*)xOffset;
    168 
    169 // Puts stuff into the final state without animating, stopping a running
    170 // animation if necessary.
    171 - (void)finalizeState;
    172 
    173 // Stops any current animation in its tracks (midway).
    174 - (void)stopCurrentAnimation;
    175 
    176 // Show/hide the bookmark bar.
    177 // if |animate| is YES, the changes are made using the animator; otherwise they
    178 // are made immediately.
    179 - (void)showBookmarkBarWithAnimation:(BOOL)animate;
    180 
    181 // Handles animating the resize of the content view. Returns YES if it handled
    182 // the animation, NO if not (and hence it should be done instantly).
    183 - (BOOL)doBookmarkBarAnimation;
    184 
    185 // |point| is in the base coordinate system of the destination window;
    186 // it comes from an id<NSDraggingInfo>. |copy| is YES if a copy is to be
    187 // made and inserted into the new location while leaving the bookmark in
    188 // the old location, otherwise move the bookmark by removing from its old
    189 // location and inserting into the new location.
    190 - (BOOL)dragBookmark:(const BookmarkNode*)sourceNode
    191                   to:(NSPoint)point
    192                 copy:(BOOL)copy;
    193 
    194 // Returns the index in the model for a drag to the location given by
    195 // |point|. This is determined by finding the first button before the center
    196 // of which |point| falls, scanning left to right. Note that, currently, only
    197 // the x-coordinate of |point| is considered. Though not currently implemented,
    198 // we may check for errors, in which case this would return negative value;
    199 // callers should check for this.
    200 - (int)indexForDragToPoint:(NSPoint)point;
    201 
    202 // Add or remove buttons to/from the bar until it is filled but not overflowed.
    203 - (void)redistributeButtonsOnBarAsNeeded;
    204 
    205 // Determine the nature of the bookmark bar contents based on the number of
    206 // buttons showing. If too many then show the off-the-side list, if none
    207 // then show the no items label.
    208 - (void)reconfigureBookmarkBar;
    209 
    210 - (void)addNode:(const BookmarkNode*)child toMenu:(NSMenu*)menu;
    211 - (void)addFolderNode:(const BookmarkNode*)node toMenu:(NSMenu*)menu;
    212 - (void)tagEmptyMenu:(NSMenu*)menu;
    213 - (void)clearMenuTagMap;
    214 - (int)preferredHeight;
    215 - (void)addButtonsToView;
    216 - (BOOL)setOtherBookmarksButtonVisibility;
    217 - (BOOL)setAppsPageShortcutButtonVisibility;
    218 - (BookmarkButton*)customBookmarkButtonForCell:(NSCell*)cell;
    219 - (void)createOtherBookmarksButton;
    220 - (void)createAppsPageShortcutButton;
    221 - (void)openAppsPage:(id)sender;
    222 - (void)centerNoItemsLabel;
    223 - (void)positionRightSideButtons;
    224 - (void)watchForExitEvent:(BOOL)watch;
    225 - (void)resetAllButtonPositionsWithAnimation:(BOOL)animate;
    226 
    227 @end
    228 
    229 @implementation BookmarkBarController
    230 
    231 @synthesize currentState = currentState_;
    232 @synthesize lastState = lastState_;
    233 @synthesize isAnimationRunning = isAnimationRunning_;
    234 @synthesize delegate = delegate_;
    235 @synthesize stateAnimationsEnabled = stateAnimationsEnabled_;
    236 @synthesize innerContentAnimationsEnabled = innerContentAnimationsEnabled_;
    237 
    238 - (id)initWithBrowser:(Browser*)browser
    239          initialWidth:(CGFloat)initialWidth
    240              delegate:(id<BookmarkBarControllerDelegate>)delegate
    241        resizeDelegate:(id<ViewResizer>)resizeDelegate {
    242   if ((self = [super initWithNibName:@"BookmarkBar"
    243                               bundle:base::mac::FrameworkBundle()])) {
    244     currentState_ = BookmarkBar::HIDDEN;
    245     lastState_ = BookmarkBar::HIDDEN;
    246 
    247     browser_ = browser;
    248     initialWidth_ = initialWidth;
    249     bookmarkModel_ = BookmarkModelFactory::GetForProfile(browser_->profile());
    250     buttons_.reset([[NSMutableArray alloc] init]);
    251     delegate_ = delegate;
    252     resizeDelegate_ = resizeDelegate;
    253     folderTarget_.reset([[BookmarkFolderTarget alloc] initWithController:self]);
    254 
    255     ResourceBundle& rb = ResourceBundle::GetSharedInstance();
    256     folderImage_.reset(
    257         rb.GetNativeImageNamed(IDR_BOOKMARK_BAR_FOLDER).CopyNSImage());
    258     defaultImage_.reset(
    259         rb.GetNativeImageNamed(IDR_DEFAULT_FAVICON).CopyNSImage());
    260 
    261     innerContentAnimationsEnabled_ = YES;
    262     stateAnimationsEnabled_ = YES;
    263 
    264     // Register for theme changes, bookmark button pulsing, ...
    265     NSNotificationCenter* defaultCenter = [NSNotificationCenter defaultCenter];
    266     [defaultCenter addObserver:self
    267                       selector:@selector(themeDidChangeNotification:)
    268                           name:kBrowserThemeDidChangeNotification
    269                         object:nil];
    270     [defaultCenter addObserver:self
    271                       selector:@selector(pulseBookmarkNotification:)
    272                           name:bookmark_button::kPulseBookmarkButtonNotification
    273                         object:nil];
    274 
    275     contextMenuController_.reset(
    276         [[BookmarkContextMenuCocoaController alloc]
    277             initWithBookmarkBarController:self]);
    278 
    279     // This call triggers an -awakeFromNib, which builds the bar, which might
    280     // use |folderImage_| and |contextMenuController_|. Ensure it happens after
    281     // |folderImage_| is loaded and |contextMenuController_| is created.
    282     [[self animatableView] setResizeDelegate:resizeDelegate];
    283   }
    284   return self;
    285 }
    286 
    287 - (Browser*)browser {
    288   return browser_;
    289 }
    290 
    291 - (BookmarkContextMenuCocoaController*)menuController {
    292   return contextMenuController_.get();
    293 }
    294 
    295 - (void)pulseBookmarkNotification:(NSNotification*)notification {
    296   NSDictionary* dict = [notification userInfo];
    297   const BookmarkNode* node = NULL;
    298   NSValue *value = [dict objectForKey:bookmark_button::kBookmarkKey];
    299   DCHECK(value);
    300   if (value)
    301     node = static_cast<const BookmarkNode*>([value pointerValue]);
    302   NSNumber* number = [dict objectForKey:bookmark_button::kBookmarkPulseFlagKey];
    303   DCHECK(number);
    304   BOOL doPulse = number ? [number boolValue] : NO;
    305 
    306   // 3 cases:
    307   // button on the bar: flash it
    308   // button in "other bookmarks" folder: flash other bookmarks
    309   // button in "off the side" folder: flash the chevron
    310   for (BookmarkButton* button in [self buttons]) {
    311     if ([button bookmarkNode] == node) {
    312       [button setIsContinuousPulsing:doPulse];
    313       return;
    314     }
    315   }
    316   if ([otherBookmarksButton_ bookmarkNode] == node) {
    317     [otherBookmarksButton_ setIsContinuousPulsing:doPulse];
    318     return;
    319   }
    320   if (node->parent() == bookmarkModel_->bookmark_bar_node()) {
    321     [offTheSideButton_ setIsContinuousPulsing:doPulse];
    322     return;
    323   }
    324 
    325   NOTREACHED() << "no bookmark button found to pulse!";
    326 }
    327 
    328 - (void)dealloc {
    329   // Clear delegate so it doesn't get called during stopAnimation.
    330   [[self animatableView] setResizeDelegate:nil];
    331 
    332   // We better stop any in-flight animation if we're being killed.
    333   [[self animatableView] stopAnimation];
    334 
    335   // Remove our view from its superview so it doesn't attempt to reference
    336   // it when the controller is gone.
    337   //TODO(dmaclach): Remove -- http://crbug.com/25845
    338   [[self view] removeFromSuperview];
    339 
    340   // Be sure there is no dangling pointer.
    341   if ([[self view] respondsToSelector:@selector(setController:)])
    342     [[self view] performSelector:@selector(setController:) withObject:nil];
    343 
    344   // For safety, make sure the buttons can no longer call us.
    345   for (BookmarkButton* button in buttons_.get()) {
    346     [button setDelegate:nil];
    347     [button setTarget:nil];
    348     [button setAction:nil];
    349   }
    350 
    351   bridge_.reset(NULL);
    352   [[NSNotificationCenter defaultCenter] removeObserver:self];
    353   [self watchForExitEvent:NO];
    354   [super dealloc];
    355 }
    356 
    357 - (void)awakeFromNib {
    358   // We default to NOT open, which means height=0.
    359   DCHECK([[self view] isHidden]);  // Hidden so it's OK to change.
    360 
    361   // Set our initial height to zero, since that is what the superview
    362   // expects.  We will resize ourselves open later if needed.
    363   [[self view] setFrame:NSMakeRect(0, 0, initialWidth_, 0)];
    364 
    365   // Complete init of the "off the side" button, as much as we can.
    366   ResourceBundle& rb = ResourceBundle::GetSharedInstance();
    367   [offTheSideButton_ setImage:
    368         rb.GetNativeImageNamed(IDR_BOOKMARK_BAR_CHEVRONS).ToNSImage()];
    369   [offTheSideButton_.draggableButton setDraggable:NO];
    370   [offTheSideButton_.draggableButton setActsOnMouseDown:YES];
    371 
    372   // We are enabled by default.
    373   barIsEnabled_ = YES;
    374 
    375   // Remember the original sizes of the 'no items' and 'import bookmarks'
    376   // fields to aid in resizing when the window frame changes.
    377   originalNoItemsRect_ = [[buttonView_ noItemTextfield] frame];
    378   originalImportBookmarksRect_ = [[buttonView_ importBookmarksButton] frame];
    379 
    380   // To make life happier when the bookmark bar is floating, the chevron is a
    381   // child of the button view.
    382   [offTheSideButton_ removeFromSuperview];
    383   [buttonView_ addSubview:offTheSideButton_];
    384 
    385   // When resized we may need to add new buttons, or remove them (if
    386   // no longer visible), or add/remove the "off the side" menu.
    387   [[self view] setPostsFrameChangedNotifications:YES];
    388   [[NSNotificationCenter defaultCenter]
    389     addObserver:self
    390        selector:@selector(frameDidChange)
    391            name:NSViewFrameDidChangeNotification
    392          object:[self view]];
    393 
    394   // Watch for things going to or from fullscreen.
    395   [[NSNotificationCenter defaultCenter]
    396     addObserver:self
    397        selector:@selector(willEnterOrLeaveFullscreen:)
    398            name:kWillEnterFullscreenNotification
    399          object:nil];
    400   [[NSNotificationCenter defaultCenter]
    401     addObserver:self
    402        selector:@selector(willEnterOrLeaveFullscreen:)
    403            name:kWillLeaveFullscreenNotification
    404          object:nil];
    405 
    406   // Don't pass ourself along (as 'self') until our init is completely
    407   // done.  Thus, this call is (almost) last.
    408   bridge_.reset(new BookmarkBarBridge(browser_->profile(), self,
    409                                       bookmarkModel_));
    410 }
    411 
    412 // Called by our main view (a BookmarkBarView) when it gets moved to a
    413 // window.  We perform operations which need to know the relevant
    414 // window (e.g. watch for a window close) so they can't be performed
    415 // earlier (such as in awakeFromNib).
    416 - (void)viewDidMoveToWindow {
    417   NSNotificationCenter* defaultCenter = [NSNotificationCenter defaultCenter];
    418 
    419   // Remove any existing notifications before registering for new ones.
    420   [defaultCenter removeObserver:self
    421                            name:NSWindowWillCloseNotification
    422                          object:nil];
    423   [defaultCenter removeObserver:self
    424                            name:NSWindowDidResignMainNotification
    425                          object:nil];
    426 
    427   [defaultCenter addObserver:self
    428                     selector:@selector(parentWindowWillClose:)
    429                         name:NSWindowWillCloseNotification
    430                       object:[[self view] window]];
    431   [defaultCenter addObserver:self
    432                     selector:@selector(parentWindowDidResignMain:)
    433                         name:NSWindowDidResignMainNotification
    434                       object:[[self view] window]];
    435 }
    436 
    437 // When going fullscreen we can run into trouble.  Our view is removed
    438 // from the non-fullscreen window before the non-fullscreen window
    439 // loses key, so our parentDidResignKey: callback never gets called.
    440 // In addition, a bookmark folder controller needs to be autoreleased
    441 // (in case it's in the event chain when closed), but the release
    442 // implicitly needs to happen while it's connected to the original
    443 // (non-fullscreen) window to "unlock bar visibility".  Such a
    444 // contract isn't honored when going fullscreen with the menu option
    445 // (not with the keyboard shortcut).  We fake it as best we can here.
    446 // We have a similar problem leaving fullscreen.
    447 - (void)willEnterOrLeaveFullscreen:(NSNotification*)notification {
    448   if (folderController_) {
    449     [self childFolderWillClose:folderController_];
    450     [self closeFolderAndStopTrackingMenus];
    451   }
    452 }
    453 
    454 // NSNotificationCenter callback.
    455 - (void)parentWindowWillClose:(NSNotification*)notification {
    456   [self closeFolderAndStopTrackingMenus];
    457 }
    458 
    459 // NSNotificationCenter callback.
    460 - (void)parentWindowDidResignMain:(NSNotification*)notification {
    461   [self closeFolderAndStopTrackingMenus];
    462 }
    463 
    464 // Change the layout of the bookmark bar's subviews in response to a visibility
    465 // change (e.g., show or hide the bar) or style change (attached or floating).
    466 - (void)layoutSubviews {
    467   NSRect frame = [[self view] frame];
    468   NSRect buttonViewFrame = NSMakeRect(0, 0, NSWidth(frame), NSHeight(frame));
    469 
    470   // Add padding to the detached bookmark bar.
    471   // The state of our morph (if any); 1 is total bubble, 0 is the regular bar.
    472   CGFloat morph = [self detachedMorphProgress];
    473   CGFloat padding = bookmarks::kNTPBookmarkBarPadding;
    474   buttonViewFrame =
    475       NSInsetRect(buttonViewFrame, morph * padding, morph * padding);
    476 
    477   [buttonView_ setFrame:buttonViewFrame];
    478 
    479   // Update bookmark button backgrounds.
    480   if ([self isAnimationRunning]) {
    481     for (NSButton* button in buttons_.get())
    482       [button setNeedsDisplay:YES];
    483   }
    484 }
    485 
    486 // We don't change a preference; we only change visibility. Preference changing
    487 // (global state) is handled in |BrowserWindowCocoa::ToggleBookmarkBar()|. We
    488 // simply update based on what we're told.
    489 - (void)updateVisibility {
    490   [self showBookmarkBarWithAnimation:NO];
    491 }
    492 
    493 - (void)updateAppsPageShortcutButtonVisibility {
    494   if (!appsPageShortcutButton_.get())
    495     return;
    496   [self setAppsPageShortcutButtonVisibility];
    497   [self resetAllButtonPositionsWithAnimation:NO];
    498   [self reconfigureBookmarkBar];
    499 }
    500 
    501 - (void)updateHiddenState {
    502   BOOL oldHidden = [[self view] isHidden];
    503   BOOL newHidden = ![self isVisible];
    504   if (oldHidden != newHidden)
    505     [[self view] setHidden:newHidden];
    506 }
    507 
    508 - (void)setBookmarkBarEnabled:(BOOL)enabled {
    509   if (enabled != barIsEnabled_) {
    510     barIsEnabled_ = enabled;
    511     [self updateVisibility];
    512   }
    513 }
    514 
    515 - (CGFloat)getDesiredToolbarHeightCompression {
    516   // Some special cases....
    517   if (!barIsEnabled_)
    518     return 0;
    519 
    520   if ([self isAnimationRunning]) {
    521     // No toolbar compression when animating between hidden and showing, nor
    522     // between showing and detached.
    523     if ([self isAnimatingBetweenState:BookmarkBar::HIDDEN
    524                              andState:BookmarkBar::SHOW] ||
    525         [self isAnimatingBetweenState:BookmarkBar::SHOW
    526                              andState:BookmarkBar::DETACHED])
    527       return 0;
    528 
    529     // If we ever need any other animation cases, code would go here.
    530   }
    531 
    532   return [self isInState:BookmarkBar::SHOW] ? bookmarks::kBookmarkBarOverlap
    533                                             : 0;
    534 }
    535 
    536 - (CGFloat)toolbarDividerOpacity {
    537   // Some special cases....
    538   if ([self isAnimationRunning]) {
    539     // In general, the toolbar shouldn't show a divider while we're animating
    540     // between showing and hidden. The exception is when our height is < 1, in
    541     // which case we can't draw it. It's all-or-nothing (no partial opacity).
    542     if ([self isAnimatingBetweenState:BookmarkBar::HIDDEN
    543                              andState:BookmarkBar::SHOW])
    544       return (NSHeight([[self view] frame]) < 1) ? 1 : 0;
    545 
    546     // The toolbar should show the divider when animating between showing and
    547     // detached (but opacity will vary).
    548     if ([self isAnimatingBetweenState:BookmarkBar::SHOW
    549                              andState:BookmarkBar::DETACHED])
    550       return static_cast<CGFloat>([self detachedMorphProgress]);
    551 
    552     // If we ever need any other animation cases, code would go here.
    553   }
    554 
    555   // In general, only show the divider when it's in the normal showing state.
    556   return [self isInState:BookmarkBar::SHOW] ? 0 : 1;
    557 }
    558 
    559 - (NSImage*)faviconForNode:(const BookmarkNode*)node {
    560   if (!node)
    561     return defaultImage_;
    562 
    563   if (node->is_folder())
    564     return folderImage_;
    565 
    566   const gfx::Image& favicon = bookmarkModel_->GetFavicon(node);
    567   if (!favicon.IsEmpty())
    568     return favicon.ToNSImage();
    569 
    570   return defaultImage_;
    571 }
    572 
    573 - (void)closeFolderAndStopTrackingMenus {
    574   showFolderMenus_ = NO;
    575   [self closeAllBookmarkFolders];
    576 }
    577 
    578 - (BOOL)canEditBookmarks {
    579   PrefService* prefs = browser_->profile()->GetPrefs();
    580   return prefs->GetBoolean(prefs::kEditBookmarksEnabled);
    581 }
    582 
    583 - (BOOL)canEditBookmark:(const BookmarkNode*)node {
    584   // Don't allow edit/delete of the permanent nodes.
    585   if (node == nil || bookmarkModel_->is_permanent_node(node))
    586     return NO;
    587   return YES;
    588 }
    589 
    590 #pragma mark Actions
    591 
    592 // Helper methods called on the main thread by runMenuFlashThread.
    593 
    594 - (void)setButtonFlashStateOn:(id)sender {
    595   [sender highlight:YES];
    596 }
    597 
    598 - (void)setButtonFlashStateOff:(id)sender {
    599   [sender highlight:NO];
    600 }
    601 
    602 - (void)cleanupAfterMenuFlashThread:(id)sender {
    603   [self closeFolderAndStopTrackingMenus];
    604 
    605   // Items retained by doMenuFlashOnSeparateThread below.
    606   [sender release];
    607   [self release];
    608 }
    609 
    610 // End runMenuFlashThread helper methods.
    611 
    612 // This call is invoked only by doMenuFlashOnSeparateThread below.
    613 // It makes the selected BookmarkButton (which is masquerading as a menu item)
    614 // flash a few times to give confirmation feedback, then it closes the menu.
    615 // It spends all its time sleeping or scheduling UI work on the main thread.
    616 - (void)runMenuFlashThread:(id)sender {
    617 
    618   // Check this is not running on the main thread, as it sleeps.
    619   DCHECK(![NSThread isMainThread]);
    620 
    621   // Duration of flash phases and number of flashes designed to evoke a
    622   // slightly retro "more mac-like than the Mac" feel.
    623   // Current Cocoa UI has a barely perceptible flash,probably because Apple
    624   // doesn't fire the action til after the animation and so there's a hurry.
    625   // As this code is fully asynchronous, it can take its time.
    626   const float kBBOnFlashTime = 0.08;
    627   const float kBBOffFlashTime = 0.08;
    628   const int kBookmarkButtonMenuFlashes = 3;
    629 
    630   for (int count = 0 ; count < kBookmarkButtonMenuFlashes ; count++) {
    631     [self performSelectorOnMainThread:@selector(setButtonFlashStateOn:)
    632                            withObject:sender
    633                         waitUntilDone:NO];
    634     [NSThread sleepForTimeInterval:kBBOnFlashTime];
    635     [self performSelectorOnMainThread:@selector(setButtonFlashStateOff:)
    636                            withObject:sender
    637                         waitUntilDone:NO];
    638     [NSThread sleepForTimeInterval:kBBOffFlashTime];
    639   }
    640   [self performSelectorOnMainThread:@selector(cleanupAfterMenuFlashThread:)
    641                          withObject:sender
    642                       waitUntilDone:NO];
    643 }
    644 
    645 // Non-blocking call which starts the process to make the selected menu item
    646 // flash a few times to give confirmation feedback, after which it closes the
    647 // menu. The item is of course actually a BookmarkButton masquerading as a menu
    648 // item).
    649 - (void)doMenuFlashOnSeparateThread:(id)sender {
    650 
    651   // Ensure that self and sender don't go away before the animation completes.
    652   // These retains are balanced in cleanupAfterMenuFlashThread above.
    653   [self retain];
    654   [sender retain];
    655   [NSThread detachNewThreadSelector:@selector(runMenuFlashThread:)
    656                            toTarget:self
    657                          withObject:sender];
    658 }
    659 
    660 - (IBAction)openBookmark:(id)sender {
    661   BOOL isMenuItem = [[sender cell] isFolderButtonCell];
    662   BOOL animate = isMenuItem && innerContentAnimationsEnabled_;
    663   if (animate)
    664     [self doMenuFlashOnSeparateThread:sender];
    665   DCHECK([sender respondsToSelector:@selector(bookmarkNode)]);
    666   const BookmarkNode* node = [sender bookmarkNode];
    667   DCHECK(node);
    668   WindowOpenDisposition disposition =
    669       ui::WindowOpenDispositionFromNSEvent([NSApp currentEvent]);
    670   RecordAppLaunch(browser_->profile(), node->url());
    671   [self openURL:node->url() disposition:disposition];
    672 
    673   if (!animate)
    674     [self closeFolderAndStopTrackingMenus];
    675   bookmark_utils::RecordBookmarkLaunch([self bookmarkLaunchLocation]);
    676 }
    677 
    678 // Common function to open a bookmark folder of any type.
    679 - (void)openBookmarkFolder:(id)sender {
    680   DCHECK([sender isKindOfClass:[BookmarkButton class]]);
    681   DCHECK([[sender cell] isKindOfClass:[BookmarkButtonCell class]]);
    682 
    683   // Only record the action if it's the initial folder being opened.
    684   if (!showFolderMenus_)
    685     bookmark_utils::RecordBookmarkFolderOpen([self bookmarkLaunchLocation]);
    686   showFolderMenus_ = !showFolderMenus_;
    687 
    688   if (sender == offTheSideButton_)
    689     [[sender cell] setStartingChildIndex:displayedButtonCount_];
    690 
    691   // Toggle presentation of bar folder menus.
    692   [folderTarget_ openBookmarkFolderFromButton:sender];
    693 }
    694 
    695 // Click on a bookmark folder button.
    696 - (IBAction)openBookmarkFolderFromButton:(id)sender {
    697   [self openBookmarkFolder:sender];
    698 }
    699 
    700 // Click on the "off the side" button (chevron), which opens like a folder
    701 // button but isn't exactly a parent folder.
    702 - (IBAction)openOffTheSideFolderFromButton:(id)sender {
    703   [self openBookmarkFolder:sender];
    704 }
    705 
    706 - (IBAction)importBookmarks:(id)sender {
    707   chrome::ShowImportDialog(browser_);
    708 }
    709 
    710 #pragma mark Private Methods
    711 
    712 // Called after a theme change took place, possibly for a different profile.
    713 - (void)themeDidChangeNotification:(NSNotification*)notification {
    714   [self updateTheme:[[[self view] window] themeProvider]];
    715 }
    716 
    717 // (Private) Method is the same as [self view], but is provided to be explicit.
    718 - (BackgroundGradientView*)backgroundGradientView {
    719   DCHECK([[self view] isKindOfClass:[BackgroundGradientView class]]);
    720   return (BackgroundGradientView*)[self view];
    721 }
    722 
    723 // (Private) Method is the same as [self view], but is provided to be explicit.
    724 - (AnimatableView*)animatableView {
    725   DCHECK([[self view] isKindOfClass:[AnimatableView class]]);
    726   return (AnimatableView*)[self view];
    727 }
    728 
    729 - (bookmark_utils::BookmarkLaunchLocation)bookmarkLaunchLocation {
    730   return currentState_ == BookmarkBar::DETACHED ?
    731       bookmark_utils::LAUNCH_DETACHED_BAR :
    732       bookmark_utils::LAUNCH_ATTACHED_BAR;
    733 }
    734 
    735 // Position the right-side buttons including the off-the-side chevron.
    736 - (void)positionRightSideButtons {
    737   int maxX = NSMaxX([[self buttonView] bounds]) -
    738       bookmarks::kBookmarkHorizontalPadding;
    739   int right = maxX;
    740 
    741   int ignored = 0;
    742   NSRect frame = [self frameForBookmarkButtonFromCell:
    743       [otherBookmarksButton_ cell] xOffset:&ignored];
    744   if (![otherBookmarksButton_ isHidden]) {
    745     right -= NSWidth(frame);
    746     frame.origin.x = right;
    747   } else {
    748     frame.origin.x = maxX - NSWidth(frame);
    749   }
    750   [otherBookmarksButton_ setFrame:frame];
    751 
    752   frame = [offTheSideButton_ frame];
    753   frame.size.height = bookmarks::kBookmarkFolderButtonHeight;
    754   right -= frame.size.width;
    755   frame.origin.x = right;
    756   [offTheSideButton_ setFrame:frame];
    757 }
    758 
    759 // Configure the off-the-side button (e.g. specify the node range,
    760 // check if we should enable or disable it, etc).
    761 - (void)configureOffTheSideButtonContentsAndVisibility {
    762   // If deleting a button while off-the-side is open, buttons may be
    763   // promoted from off-the-side to the bar.  Accomodate.
    764   if (folderController_ &&
    765       ([folderController_ parentButton] == offTheSideButton_)) {
    766     [folderController_ reconfigureMenu];
    767   }
    768 
    769   [[offTheSideButton_ cell] setStartingChildIndex:displayedButtonCount_];
    770   [[offTheSideButton_ cell]
    771    setBookmarkNode:bookmarkModel_->bookmark_bar_node()];
    772   int bookmarkChildren = bookmarkModel_->bookmark_bar_node()->child_count();
    773   if (bookmarkChildren > displayedButtonCount_) {
    774     [offTheSideButton_ setHidden:NO];
    775   } else {
    776     // If we just deleted the last item in an off-the-side menu so the
    777     // button will be going away, make sure the menu goes away.
    778     if (folderController_ &&
    779         ([folderController_ parentButton] == offTheSideButton_))
    780       [self closeAllBookmarkFolders];
    781     // (And hide the button, too.)
    782     [offTheSideButton_ setHidden:YES];
    783   }
    784 }
    785 
    786 // Main menubar observation code, so we can know to close our fake menus if the
    787 // user clicks on the actual menubar, as multiple unconnected menus sharing
    788 // the screen looks weird.
    789 // Needed because the local event monitor doesn't see the click on the menubar.
    790 
    791 // Gets called when the menubar is clicked.
    792 - (void)begunTracking:(NSNotification *)notification {
    793   [self closeFolderAndStopTrackingMenus];
    794 }
    795 
    796 // Install the callback.
    797 - (void)startObservingMenubar {
    798   NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
    799   [nc addObserver:self
    800          selector:@selector(begunTracking:)
    801              name:NSMenuDidBeginTrackingNotification
    802            object:[NSApp mainMenu]];
    803 }
    804 
    805 // Remove the callback.
    806 - (void)stopObservingMenubar {
    807   NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
    808   [nc removeObserver:self
    809                 name:NSMenuDidBeginTrackingNotification
    810               object:[NSApp mainMenu]];
    811 }
    812 
    813 // End of menubar observation code.
    814 
    815 // Begin (or end) watching for a click outside this window.  Unlike
    816 // normal NSWindows, bookmark folder "fake menu" windows do not become
    817 // key or main.  Thus, traditional notification (e.g. WillResignKey)
    818 // won't work.  Our strategy is to watch (at the app level) for a
    819 // "click outside" these windows to detect when they logically lose
    820 // focus.
    821 - (void)watchForExitEvent:(BOOL)watch {
    822   if (watch) {
    823     if (!exitEventTap_) {
    824       exitEventTap_ = [NSEvent
    825           addLocalMonitorForEventsMatchingMask:NSAnyEventMask
    826           handler:^NSEvent* (NSEvent* event) {
    827               if ([self isEventAnExitEvent:event])
    828                 [self closeFolderAndStopTrackingMenus];
    829               return event;
    830           }];
    831       [self startObservingMenubar];
    832     }
    833   } else {
    834     if (exitEventTap_) {
    835       [NSEvent removeMonitor:exitEventTap_];
    836       exitEventTap_ = nil;
    837       [self stopObservingMenubar];
    838     }
    839   }
    840 }
    841 
    842 // Keep the "no items" label centered in response to a frame size change.
    843 - (void)centerNoItemsLabel {
    844   // Note that this computation is done in the parent's coordinate system,
    845   // which is unflipped. Also, we want the label to be a fixed distance from
    846   // the bottom, so that it slides up properly (on animating to hidden).
    847   // The textfield sits in the itemcontainer, so to center it we maintain
    848   // equal vertical padding on the top and bottom.
    849   int yoffset = (NSHeight([[buttonView_ noItemTextfield] frame]) -
    850                  NSHeight([[buttonView_ noItemContainer] frame])) / 2;
    851   [[buttonView_ noItemContainer] setFrameOrigin:NSMakePoint(0, yoffset)];
    852 }
    853 
    854 // (Private)
    855 - (void)showBookmarkBarWithAnimation:(BOOL)animate {
    856   if (animate && stateAnimationsEnabled_) {
    857     // If |-doBookmarkBarAnimation| does the animation, we're done.
    858     if ([self doBookmarkBarAnimation])
    859       return;
    860 
    861     // Else fall through and do the change instantly.
    862   }
    863 
    864   // Set our height.
    865   [resizeDelegate_ resizeView:[self view]
    866                     newHeight:[self preferredHeight]];
    867 
    868   // Only show the divider if showing the normal bookmark bar.
    869   BOOL showsDivider = [self isInState:BookmarkBar::SHOW];
    870   [[self backgroundGradientView] setShowsDivider:showsDivider];
    871 
    872   // Make sure we're shown.
    873   [[self view] setHidden:![self isVisible]];
    874 
    875   // Update everything else.
    876   [self layoutSubviews];
    877   [self frameDidChange];
    878 }
    879 
    880 // (Private)
    881 - (BOOL)doBookmarkBarAnimation {
    882   if ([self isAnimatingFromState:BookmarkBar::HIDDEN
    883                          toState:BookmarkBar::SHOW]) {
    884     [[self backgroundGradientView] setShowsDivider:YES];
    885     [[self view] setHidden:NO];
    886     AnimatableView* view = [self animatableView];
    887     // Height takes into account the extra height we have since the toolbar
    888     // only compresses when we're done.
    889     [view animateToNewHeight:(bookmarks::kBookmarkBarHeight -
    890                               bookmarks::kBookmarkBarOverlap)
    891                     duration:kBookmarkBarAnimationDuration];
    892   } else if ([self isAnimatingFromState:BookmarkBar::SHOW
    893                                 toState:BookmarkBar::HIDDEN]) {
    894     [[self backgroundGradientView] setShowsDivider:YES];
    895     [[self view] setHidden:NO];
    896     AnimatableView* view = [self animatableView];
    897     [view animateToNewHeight:0
    898                     duration:kBookmarkBarAnimationDuration];
    899   } else if ([self isAnimatingFromState:BookmarkBar::SHOW
    900                                 toState:BookmarkBar::DETACHED]) {
    901     [[self backgroundGradientView] setShowsDivider:YES];
    902     [[self view] setHidden:NO];
    903     AnimatableView* view = [self animatableView];
    904     [view animateToNewHeight:chrome::kNTPBookmarkBarHeight
    905                     duration:kBookmarkBarAnimationDuration];
    906   } else if ([self isAnimatingFromState:BookmarkBar::DETACHED
    907                                 toState:BookmarkBar::SHOW]) {
    908     [[self backgroundGradientView] setShowsDivider:YES];
    909     [[self view] setHidden:NO];
    910     AnimatableView* view = [self animatableView];
    911     // Height takes into account the extra height we have since the toolbar
    912     // only compresses when we're done.
    913     [view animateToNewHeight:(bookmarks::kBookmarkBarHeight -
    914                               bookmarks::kBookmarkBarOverlap)
    915                     duration:kBookmarkBarAnimationDuration];
    916   } else {
    917     // Oops! An animation we don't know how to handle.
    918     return NO;
    919   }
    920 
    921   return YES;
    922 }
    923 
    924 // Actually open the URL.  This is the last chance for a unit test to
    925 // override.
    926 - (void)openURL:(GURL)url disposition:(WindowOpenDisposition)disposition {
    927   OpenURLParams params(
    928       url, Referrer(), disposition, content::PAGE_TRANSITION_AUTO_BOOKMARK,
    929       false);
    930   browser_->OpenURL(params);
    931 }
    932 
    933 - (void)clearMenuTagMap {
    934   seedId_ = 0;
    935   menuTagMap_.clear();
    936 }
    937 
    938 - (int)preferredHeight {
    939   DCHECK(![self isAnimationRunning]);
    940 
    941   if (!barIsEnabled_)
    942     return 0;
    943 
    944   switch (currentState_) {
    945     case BookmarkBar::SHOW:
    946       return bookmarks::kBookmarkBarHeight;
    947     case BookmarkBar::DETACHED:
    948       return chrome::kNTPBookmarkBarHeight;
    949     case BookmarkBar::HIDDEN:
    950       return 0;
    951   }
    952 }
    953 
    954 // Recursively add the given bookmark node and all its children to
    955 // menu, one menu item per node.
    956 - (void)addNode:(const BookmarkNode*)child toMenu:(NSMenu*)menu {
    957   NSString* title = [BookmarkMenuCocoaController menuTitleForNode:child];
    958   NSMenuItem* item = [[[NSMenuItem alloc] initWithTitle:title
    959                                                  action:nil
    960                                           keyEquivalent:@""] autorelease];
    961   [menu addItem:item];
    962   [item setImage:[self faviconForNode:child]];
    963   if (child->is_folder()) {
    964     NSMenu* submenu = [[[NSMenu alloc] initWithTitle:title] autorelease];
    965     [menu setSubmenu:submenu forItem:item];
    966     if (!child->empty()) {
    967       [self addFolderNode:child toMenu:submenu];  // potentially recursive
    968     } else {
    969       [self tagEmptyMenu:submenu];
    970     }
    971   } else {
    972     [item setTarget:self];
    973     [item setAction:@selector(openBookmarkMenuItem:)];
    974     [item setTag:[self menuTagFromNodeId:child->id()]];
    975     if (child->is_url())
    976       [item setToolTip:[BookmarkMenuCocoaController tooltipForNode:child]];
    977   }
    978 }
    979 
    980 // Empty menus are odd; if empty, add something to look at.
    981 // Matches windows behavior.
    982 - (void)tagEmptyMenu:(NSMenu*)menu {
    983   NSString* empty_menu_title = l10n_util::GetNSString(IDS_MENU_EMPTY_SUBMENU);
    984   [menu addItem:[[[NSMenuItem alloc] initWithTitle:empty_menu_title
    985                                             action:NULL
    986                                      keyEquivalent:@""] autorelease]];
    987 }
    988 
    989 // Add the children of the given bookmark node (and their children...)
    990 // to menu, one menu item per node.
    991 - (void)addFolderNode:(const BookmarkNode*)node toMenu:(NSMenu*)menu {
    992   for (int i = 0; i < node->child_count(); i++) {
    993     const BookmarkNode* child = node->GetChild(i);
    994     [self addNode:child toMenu:menu];
    995   }
    996 }
    997 
    998 // Return an autoreleased NSMenu that represents the given bookmark
    999 // folder node.
   1000 - (NSMenu *)menuForFolderNode:(const BookmarkNode*)node {
   1001   if (!node->is_folder())
   1002     return nil;
   1003   NSString* title = base::SysUTF16ToNSString(node->GetTitle());
   1004   NSMenu* menu = [[[NSMenu alloc] initWithTitle:title] autorelease];
   1005   [self addFolderNode:node toMenu:menu];
   1006 
   1007   if (![menu numberOfItems]) {
   1008     [self tagEmptyMenu:menu];
   1009   }
   1010   return menu;
   1011 }
   1012 
   1013 // Return an appropriate width for the given bookmark button cell.
   1014 // The "+2" is needed because, sometimes, Cocoa is off by a tad.
   1015 // Example: for a bookmark named "Moma" or "SFGate", it is one pixel
   1016 // too small.  For "FBL" it is 2 pixels too small.
   1017 // For a bookmark named "SFGateFooWoo", it is just fine.
   1018 - (CGFloat)widthForBookmarkButtonCell:(NSCell*)cell {
   1019   CGFloat desired = [cell cellSize].width + 2;
   1020   return std::min(desired, bookmarks::kDefaultBookmarkWidth);
   1021 }
   1022 
   1023 - (IBAction)openBookmarkMenuItem:(id)sender {
   1024   int64 tag = [self nodeIdFromMenuTag:[sender tag]];
   1025   const BookmarkNode* node = bookmarkModel_->GetNodeByID(tag);
   1026   WindowOpenDisposition disposition =
   1027       ui::WindowOpenDispositionFromNSEvent([NSApp currentEvent]);
   1028   [self openURL:node->url() disposition:disposition];
   1029 }
   1030 
   1031 // For the given root node of the bookmark bar, show or hide (as
   1032 // appropriate) the "no items" container (text which says "bookmarks
   1033 // go here").
   1034 - (void)showOrHideNoItemContainerForNode:(const BookmarkNode*)node {
   1035   BOOL hideNoItemWarning = !node->empty();
   1036   [[buttonView_ noItemContainer] setHidden:hideNoItemWarning];
   1037 }
   1038 
   1039 // TODO(jrg): write a "build bar" so there is a nice spot for things
   1040 // like the contextual menu which is invoked when not over a
   1041 // bookmark.  On Safari that menu has a "new folder" option.
   1042 - (void)addNodesToButtonList:(const BookmarkNode*)node {
   1043   [self showOrHideNoItemContainerForNode:node];
   1044 
   1045   CGFloat maxViewX = NSMaxX([[self view] bounds]);
   1046   int xOffset =
   1047       bookmarks::kBookmarkLeftMargin - bookmarks::kBookmarkHorizontalPadding;
   1048 
   1049   // Draw the apps bookmark if needed.
   1050   if (![appsPageShortcutButton_ isHidden]) {
   1051     NSRect frame =
   1052         [self frameForBookmarkButtonFromCell:[appsPageShortcutButton_ cell]
   1053                                      xOffset:&xOffset];
   1054     [appsPageShortcutButton_ setFrame:frame];
   1055   }
   1056 
   1057   for (int i = 0; i < node->child_count(); i++) {
   1058     const BookmarkNode* child = node->GetChild(i);
   1059     BookmarkButton* button = [self buttonForNode:child xOffset:&xOffset];
   1060     if (NSMinX([button frame]) >= maxViewX) {
   1061       [button setDelegate:nil];
   1062       break;
   1063     }
   1064     [buttons_ addObject:button];
   1065   }
   1066 }
   1067 
   1068 - (BookmarkButton*)buttonForNode:(const BookmarkNode*)node
   1069                          xOffset:(int*)xOffset {
   1070   BookmarkButtonCell* cell = [self cellForBookmarkNode:node];
   1071   NSRect frame = [self frameForBookmarkButtonFromCell:cell xOffset:xOffset];
   1072 
   1073   base::scoped_nsobject<BookmarkButton> button(
   1074       [[BookmarkButton alloc] initWithFrame:frame]);
   1075   DCHECK(button.get());
   1076 
   1077   // [NSButton setCell:] warns to NOT use setCell: other than in the
   1078   // initializer of a control.  However, we are using a basic
   1079   // NSButton whose initializer does not take an NSCell as an
   1080   // object.  To honor the assumed semantics, we do nothing with
   1081   // NSButton between alloc/init and setCell:.
   1082   [button setCell:cell];
   1083   [button setDelegate:self];
   1084 
   1085   // We cannot set the button cell's text color until it is placed in
   1086   // the button (e.g. the [button setCell:cell] call right above).  We
   1087   // also cannot set the cell's text color until the view is added to
   1088   // the hierarchy.  If that second part is now true, set the color.
   1089   // (If not we'll set the color on the 1st themeChanged:
   1090   // notification.)
   1091   ui::ThemeProvider* themeProvider = [[[self view] window] themeProvider];
   1092   if (themeProvider) {
   1093     NSColor* color =
   1094         themeProvider->GetNSColor(ThemeProperties::COLOR_BOOKMARK_TEXT);
   1095     [cell setTextColor:color];
   1096   }
   1097 
   1098   if (node->is_folder()) {
   1099     [button setTarget:self];
   1100     [button setAction:@selector(openBookmarkFolderFromButton:)];
   1101     [[button draggableButton] setActsOnMouseDown:YES];
   1102     // If it has a title, and it will be truncated, show full title in
   1103     // tooltip.
   1104     NSString* title = base::SysUTF16ToNSString(node->GetTitle());
   1105     if ([title length] &&
   1106         [[button cell] cellSize].width > bookmarks::kDefaultBookmarkWidth) {
   1107       [button setToolTip:title];
   1108     }
   1109   } else {
   1110     // Make the button do something
   1111     [button setTarget:self];
   1112     [button setAction:@selector(openBookmark:)];
   1113     if (node->is_url())
   1114       [button setToolTip:[BookmarkMenuCocoaController tooltipForNode:node]];
   1115   }
   1116   return [[button.get() retain] autorelease];
   1117 }
   1118 
   1119 // Add bookmark buttons to the view only if they are completely
   1120 // visible and don't overlap the "other bookmarks".  Remove buttons
   1121 // which are clipped.  Called when building the bookmark bar the first time.
   1122 - (void)addButtonsToView {
   1123   displayedButtonCount_ = 0;
   1124   NSMutableArray* buttons = [self buttons];
   1125   for (NSButton* button in buttons) {
   1126     if (NSMaxX([button frame]) > (NSMinX([offTheSideButton_ frame]) -
   1127                                   bookmarks::kBookmarkHorizontalPadding))
   1128       break;
   1129     [buttonView_ addSubview:button];
   1130     ++displayedButtonCount_;
   1131   }
   1132   NSUInteger removalCount =
   1133       [buttons count] - (NSUInteger)displayedButtonCount_;
   1134   if (removalCount > 0) {
   1135     NSRange removalRange = NSMakeRange(displayedButtonCount_, removalCount);
   1136     [buttons removeObjectsInRange:removalRange];
   1137   }
   1138 }
   1139 
   1140 // Shows or hides the Other Bookmarks button as appropriate, and returns
   1141 // whether it ended up visible.
   1142 - (BOOL)setOtherBookmarksButtonVisibility {
   1143   if (!otherBookmarksButton_.get())
   1144     return NO;
   1145 
   1146   BOOL visible = ![otherBookmarksButton_ bookmarkNode]->empty();
   1147   [otherBookmarksButton_ setHidden:!visible];
   1148   return visible;
   1149 }
   1150 
   1151 // Shows or hides the Apps button as appropriate, and returns whether it ended
   1152 // up visible.
   1153 - (BOOL)setAppsPageShortcutButtonVisibility {
   1154   if (!appsPageShortcutButton_.get())
   1155     return NO;
   1156 
   1157   BOOL visible = bookmarkModel_->loaded() &&
   1158       chrome::ShouldShowAppsShortcutInBookmarkBar(browser_->profile());
   1159   [appsPageShortcutButton_ setHidden:!visible];
   1160   return visible;
   1161 }
   1162 
   1163 // Creates a bookmark bar button that does not correspond to a regular bookmark
   1164 // or folder. It is used by the "Other Bookmarks" and the "Apps" buttons.
   1165 - (BookmarkButton*)customBookmarkButtonForCell:(NSCell*)cell {
   1166   BookmarkButton* button = [[BookmarkButton alloc] init];
   1167   [[button draggableButton] setDraggable:NO];
   1168   [[button draggableButton] setActsOnMouseDown:YES];
   1169   [button setCell:cell];
   1170   [button setDelegate:self];
   1171   [button setTarget:self];
   1172   // Make sure this button, like all other BookmarkButtons, lives
   1173   // until the end of the current event loop.
   1174   [[button retain] autorelease];
   1175   return button;
   1176 }
   1177 
   1178 // Creates the button for "Other Bookmarks", but does not position it.
   1179 - (void)createOtherBookmarksButton {
   1180   // Can't create this until the model is loaded, but only need to
   1181   // create it once.
   1182   if (otherBookmarksButton_.get()) {
   1183     [self setOtherBookmarksButtonVisibility];
   1184     return;
   1185   }
   1186 
   1187   NSCell* cell = [self cellForBookmarkNode:bookmarkModel_->other_node()];
   1188   otherBookmarksButton_.reset([self customBookmarkButtonForCell:cell]);
   1189   // Peg at right; keep same height as bar.
   1190   [otherBookmarksButton_ setAutoresizingMask:(NSViewMinXMargin)];
   1191   [otherBookmarksButton_ setAction:@selector(openBookmarkFolderFromButton:)];
   1192   view_id_util::SetID(otherBookmarksButton_.get(), VIEW_ID_OTHER_BOOKMARKS);
   1193   [buttonView_ addSubview:otherBookmarksButton_.get()];
   1194 
   1195   [self setOtherBookmarksButtonVisibility];
   1196 }
   1197 
   1198 // Creates the button for "Apps", but does not position it.
   1199 - (void)createAppsPageShortcutButton {
   1200   // Can't create this until the model is loaded, but only need to
   1201   // create it once.
   1202   if (appsPageShortcutButton_.get()) {
   1203     [self setAppsPageShortcutButtonVisibility];
   1204     return;
   1205   }
   1206 
   1207   ResourceBundle& rb = ResourceBundle::GetSharedInstance();
   1208   NSString* text = l10n_util::GetNSString(IDS_BOOKMARK_BAR_APPS_SHORTCUT_NAME);
   1209   NSImage* image = rb.GetNativeImageNamed(
   1210       IDR_BOOKMARK_BAR_APPS_SHORTCUT).ToNSImage();
   1211   NSCell* cell = [self cellForCustomButtonWithText:text
   1212                                              image:image];
   1213   appsPageShortcutButton_.reset([self customBookmarkButtonForCell:cell]);
   1214   [[appsPageShortcutButton_ draggableButton] setActsOnMouseDown:NO];
   1215   [appsPageShortcutButton_ setAction:@selector(openAppsPage:)];
   1216   NSString* tooltip =
   1217       l10n_util::GetNSString(IDS_BOOKMARK_BAR_APPS_SHORTCUT_TOOLTIP);
   1218   [appsPageShortcutButton_ setToolTip:tooltip];
   1219   [buttonView_ addSubview:appsPageShortcutButton_.get()];
   1220 
   1221   [self setAppsPageShortcutButtonVisibility];
   1222 }
   1223 
   1224 - (void)openAppsPage:(id)sender {
   1225   WindowOpenDisposition disposition =
   1226       ui::WindowOpenDispositionFromNSEvent([NSApp currentEvent]);
   1227   [self openURL:GURL(chrome::kChromeUIAppsURL) disposition:disposition];
   1228   bookmark_utils::RecordAppsPageOpen([self bookmarkLaunchLocation]);
   1229 }
   1230 
   1231 // To avoid problems with sync, changes that may impact the current
   1232 // bookmark (e.g. deletion) make sure context menus are closed.  This
   1233 // prevents deleting a node which no longer exists.
   1234 - (void)cancelMenuTracking {
   1235   [contextMenuController_ cancelTracking];
   1236 }
   1237 
   1238 - (void)moveToState:(BookmarkBar::State)nextState
   1239       withAnimation:(BOOL)animate {
   1240   BOOL isAnimationRunning = [self isAnimationRunning];
   1241 
   1242   // No-op if the next state is the same as the "current" one, subject to the
   1243   // following conditions:
   1244   //  - no animation is running; or
   1245   //  - an animation is running and |animate| is YES ([*] if it's NO, we'd want
   1246   //    to cancel the animation and jump to the final state).
   1247   if ((nextState == currentState_) && (!isAnimationRunning || animate))
   1248     return;
   1249 
   1250   // If an animation is running, we want to finalize it. Otherwise we'd have to
   1251   // be able to animate starting from the middle of one type of animation. We
   1252   // assume that animations that we know about can be "reversed".
   1253   if (isAnimationRunning) {
   1254     // Don't cancel if we're going to reverse the animation.
   1255     if (nextState != lastState_) {
   1256       [self stopCurrentAnimation];
   1257       [self finalizeState];
   1258     }
   1259 
   1260     // If we're in case [*] above, we can stop here.
   1261     if (nextState == currentState_)
   1262       return;
   1263   }
   1264 
   1265   // Now update with the new state change.
   1266   lastState_ = currentState_;
   1267   currentState_ = nextState;
   1268   isAnimationRunning_ = YES;
   1269 
   1270   // Animate only if told to and if bar is enabled.
   1271   if (animate && stateAnimationsEnabled_ && barIsEnabled_) {
   1272     [self closeAllBookmarkFolders];
   1273     // Take care of any animation cases we know how to handle.
   1274 
   1275     // We know how to handle hidden <-> normal, normal <-> detached....
   1276     if ([self isAnimatingBetweenState:BookmarkBar::HIDDEN
   1277                              andState:BookmarkBar::SHOW] ||
   1278         [self isAnimatingBetweenState:BookmarkBar::SHOW
   1279                              andState:BookmarkBar::DETACHED]) {
   1280       [delegate_ bookmarkBar:self
   1281         willAnimateFromState:lastState_
   1282                      toState:currentState_];
   1283       [self showBookmarkBarWithAnimation:YES];
   1284       return;
   1285     }
   1286 
   1287     // If we ever need any other animation cases, code would go here.
   1288     // Let any animation cases which we don't know how to handle fall through to
   1289     // the unanimated case.
   1290   }
   1291 
   1292   // Just jump to the state.
   1293   [self finalizeState];
   1294 }
   1295 
   1296 // N.B.: |-moveToState:...| will check if this should be a no-op or not.
   1297 - (void)updateState:(BookmarkBar::State)newState
   1298          changeType:(BookmarkBar::AnimateChangeType)changeType {
   1299   BOOL animate = changeType == BookmarkBar::ANIMATE_STATE_CHANGE &&
   1300                  stateAnimationsEnabled_;
   1301   [self moveToState:newState withAnimation:animate];
   1302 }
   1303 
   1304 // (Private)
   1305 - (void)finalizeState {
   1306   // We promise that our delegate that the variables will be finalized before
   1307   // the call to |-bookmarkBar:didChangeFromState:toState:|.
   1308   BookmarkBar::State oldState = lastState_;
   1309   lastState_ = currentState_;
   1310   isAnimationRunning_ = NO;
   1311 
   1312   // Notify our delegate.
   1313   [delegate_ bookmarkBar:self
   1314       didChangeFromState:oldState
   1315                  toState:currentState_];
   1316 
   1317   // Update ourselves visually.
   1318   [self updateVisibility];
   1319 }
   1320 
   1321 // (Private)
   1322 - (void)stopCurrentAnimation {
   1323   [[self animatableView] stopAnimation];
   1324 }
   1325 
   1326 // Delegate method for |AnimatableView| (a superclass of
   1327 // |BookmarkBarToolbarView|).
   1328 - (void)animationDidEnd:(NSAnimation*)animation {
   1329   [self finalizeState];
   1330 }
   1331 
   1332 - (void)reconfigureBookmarkBar {
   1333   [self redistributeButtonsOnBarAsNeeded];
   1334   [self positionRightSideButtons];
   1335   [self configureOffTheSideButtonContentsAndVisibility];
   1336   [self centerNoItemsLabel];
   1337 }
   1338 
   1339 // Determine if the given |view| can completely fit within the constraint of
   1340 // maximum x, given by |maxViewX|, and, if not, narrow the view up to a minimum
   1341 // width. If the minimum width is not achievable then hide the view. Return YES
   1342 // if the view was hidden.
   1343 - (BOOL)shrinkOrHideView:(NSView*)view forMaxX:(CGFloat)maxViewX {
   1344   BOOL wasHidden = NO;
   1345   // See if the view needs to be narrowed.
   1346   NSRect frame = [view frame];
   1347   if (NSMaxX(frame) > maxViewX) {
   1348     // Resize if more than 30 pixels are showing, otherwise hide.
   1349     if (NSMinX(frame) + 30.0 < maxViewX) {
   1350       frame.size.width = maxViewX - NSMinX(frame);
   1351       [view setFrame:frame];
   1352     } else {
   1353       [view setHidden:YES];
   1354       wasHidden = YES;
   1355     }
   1356   }
   1357   return wasHidden;
   1358 }
   1359 
   1360 // Bookmark button menu items that open a new window (e.g., open in new window,
   1361 // open in incognito, edit, etc.) cause us to lose a mouse-exited event
   1362 // on the button, which leaves it in a hover state.
   1363 // Since the showsBorderOnlyWhileMouseInside uses a tracking area, simple
   1364 // tricks (e.g. sending an extra mouseExited: to the button) don't
   1365 // fix the problem.
   1366 // http://crbug.com/129338
   1367 - (void)unhighlightBookmark:(const BookmarkNode*)node {
   1368   // Only relevant if context menu was opened from a button on the
   1369   // bookmark bar.
   1370   const BookmarkNode* parent = node->parent();
   1371   BookmarkNode::Type parentType = parent->type();
   1372   if (parentType == BookmarkNode::BOOKMARK_BAR) {
   1373     int index = parent->GetIndexOf(node);
   1374     if ((index >= 0) && (static_cast<NSUInteger>(index) < [buttons_ count])) {
   1375       NSButton* button =
   1376           [buttons_ objectAtIndex:static_cast<NSUInteger>(index)];
   1377       if ([button showsBorderOnlyWhileMouseInside]) {
   1378         [button setShowsBorderOnlyWhileMouseInside:NO];
   1379         [button setShowsBorderOnlyWhileMouseInside:YES];
   1380       }
   1381     }
   1382   }
   1383 }
   1384 
   1385 
   1386 // Adjust the horizontal width, x position and the visibility of the "For quick
   1387 // access" text field and "Import bookmarks..." button based on the current
   1388 // width of the containing |buttonView_| (which is affected by window width).
   1389 - (void)adjustNoItemContainerForMaxX:(CGFloat)maxViewX {
   1390   if (![[buttonView_ noItemContainer] isHidden]) {
   1391     // Reset initial frames for the two items, then adjust as necessary.
   1392     NSTextField* noItemTextfield = [buttonView_ noItemTextfield];
   1393     NSRect noItemsRect = originalNoItemsRect_;
   1394     NSRect importBookmarksRect = originalImportBookmarksRect_;
   1395     if (![appsPageShortcutButton_ isHidden]) {
   1396       float width = NSWidth([appsPageShortcutButton_ frame]);
   1397       noItemsRect.origin.x += width;
   1398       importBookmarksRect.origin.x += width;
   1399     }
   1400     [noItemTextfield setFrame:noItemsRect];
   1401     [noItemTextfield setHidden:NO];
   1402     NSButton* importBookmarksButton = [buttonView_ importBookmarksButton];
   1403     [importBookmarksButton setFrame:importBookmarksRect];
   1404     [importBookmarksButton setHidden:NO];
   1405     // Check each to see if they need to be shrunk or hidden.
   1406     if ([self shrinkOrHideView:importBookmarksButton forMaxX:maxViewX])
   1407       [self shrinkOrHideView:noItemTextfield forMaxX:maxViewX];
   1408   }
   1409 }
   1410 
   1411 // Scans through all buttons from left to right, calculating from scratch where
   1412 // they should be based on the preceding widths, until it finds the one
   1413 // requested.
   1414 // Returns NSZeroRect if there is no such button in the bookmark bar.
   1415 // Enables you to work out where a button will end up when it is done animating.
   1416 - (NSRect)finalRectOfButton:(BookmarkButton*)wantedButton {
   1417   CGFloat left = bookmarks::kBookmarkLeftMargin;
   1418   NSRect buttonFrame = NSZeroRect;
   1419 
   1420   // Draw the apps bookmark if needed.
   1421   if (![appsPageShortcutButton_ isHidden]) {
   1422     left = NSMaxX([appsPageShortcutButton_ frame]) +
   1423         bookmarks::kBookmarkHorizontalPadding;
   1424   }
   1425 
   1426   for (NSButton* button in buttons_.get()) {
   1427     // Hidden buttons get no space.
   1428     if ([button isHidden])
   1429       continue;
   1430     buttonFrame = [button frame];
   1431     buttonFrame.origin.x = left;
   1432     left += buttonFrame.size.width + bookmarks::kBookmarkHorizontalPadding;
   1433     if (button == wantedButton)
   1434       return buttonFrame;
   1435   }
   1436   return NSZeroRect;
   1437 }
   1438 
   1439 // Calculates the final position of the last button in the bar.
   1440 // We can't just use [[self buttons] lastObject] frame] because the button
   1441 // may be animating currently.
   1442 - (NSRect)finalRectOfLastButton {
   1443   return [self finalRectOfButton:[[self buttons] lastObject]];
   1444 }
   1445 
   1446 - (CGFloat)buttonViewMaxXWithOffTheSideButtonIsVisible:(BOOL)visible {
   1447   CGFloat maxViewX = NSMaxX([buttonView_ bounds]);
   1448   // If necessary, pull in the width to account for the Other Bookmarks button.
   1449   if ([self setOtherBookmarksButtonVisibility]) {
   1450     maxViewX = [otherBookmarksButton_ frame].origin.x -
   1451         bookmarks::kBookmarkRightMargin;
   1452   }
   1453 
   1454   [self positionRightSideButtons];
   1455   // If we're already overflowing, then we need to account for the chevron.
   1456   if (visible) {
   1457     maxViewX =
   1458         [offTheSideButton_ frame].origin.x - bookmarks::kBookmarkRightMargin;
   1459   }
   1460 
   1461   return maxViewX;
   1462 }
   1463 
   1464 - (void)redistributeButtonsOnBarAsNeeded {
   1465   const BookmarkNode* node = bookmarkModel_->bookmark_bar_node();
   1466   NSInteger barCount = node->child_count();
   1467 
   1468   // Determine the current maximum extent of the visible buttons.
   1469   [self positionRightSideButtons];
   1470   BOOL offTheSideButtonVisible = (barCount > displayedButtonCount_);
   1471   CGFloat maxViewX = [self buttonViewMaxXWithOffTheSideButtonIsVisible:
   1472       offTheSideButtonVisible];
   1473 
   1474   // As a result of pasting or dragging, the bar may now have more buttons
   1475   // than will fit so remove any which overflow.  They will be shown in
   1476   // the off-the-side folder.
   1477   while (displayedButtonCount_ > 0) {
   1478     BookmarkButton* button = [buttons_ lastObject];
   1479     if (NSMaxX([self finalRectOfLastButton]) < maxViewX)
   1480       break;
   1481     [buttons_ removeLastObject];
   1482     [button setDelegate:nil];
   1483     [button removeFromSuperview];
   1484     --displayedButtonCount_;
   1485     // Account for the fact that the chevron might now be visible.
   1486     if (!offTheSideButtonVisible) {
   1487       offTheSideButtonVisible = YES;
   1488       maxViewX = [self buttonViewMaxXWithOffTheSideButtonIsVisible:YES];
   1489     }
   1490   }
   1491 
   1492   // As a result of cutting, deleting and dragging, the bar may now have room
   1493   // for more buttons.
   1494   int xOffset;
   1495   if (displayedButtonCount_ > 0) {
   1496     xOffset = NSMaxX([self finalRectOfLastButton]) +
   1497         bookmarks::kBookmarkHorizontalPadding;
   1498   } else if (![appsPageShortcutButton_ isHidden]) {
   1499     xOffset = NSMaxX([appsPageShortcutButton_ frame]) +
   1500         bookmarks::kBookmarkHorizontalPadding;
   1501   } else {
   1502     xOffset = bookmarks::kBookmarkLeftMargin -
   1503         bookmarks::kBookmarkHorizontalPadding;
   1504   }
   1505   for (int i = displayedButtonCount_; i < barCount; ++i) {
   1506     const BookmarkNode* child = node->GetChild(i);
   1507     BookmarkButton* button = [self buttonForNode:child xOffset:&xOffset];
   1508     // If we're testing against the last possible button then account
   1509     // for the chevron no longer needing to be shown.
   1510     if (i == barCount - 1)
   1511       maxViewX = [self buttonViewMaxXWithOffTheSideButtonIsVisible:NO];
   1512     if (NSMaxX([button frame]) > maxViewX) {
   1513       [button setDelegate:nil];
   1514       break;
   1515     }
   1516     ++displayedButtonCount_;
   1517     [buttons_ addObject:button];
   1518     [buttonView_ addSubview:button];
   1519   }
   1520 
   1521   // While we're here, adjust the horizontal width and the visibility
   1522   // of the "For quick access" and "Import bookmarks..." text fields.
   1523   if (![buttons_ count])
   1524     [self adjustNoItemContainerForMaxX:maxViewX];
   1525 }
   1526 
   1527 #pragma mark Private Methods Exposed for Testing
   1528 
   1529 - (BookmarkBarView*)buttonView {
   1530   return buttonView_;
   1531 }
   1532 
   1533 - (NSMutableArray*)buttons {
   1534   return buttons_.get();
   1535 }
   1536 
   1537 - (NSButton*)offTheSideButton {
   1538   return offTheSideButton_;
   1539 }
   1540 
   1541 - (NSButton*)appsPageShortcutButton {
   1542   return appsPageShortcutButton_;
   1543 }
   1544 
   1545 - (BOOL)offTheSideButtonIsHidden {
   1546   return [offTheSideButton_ isHidden];
   1547 }
   1548 
   1549 - (BOOL)appsPageShortcutButtonIsHidden {
   1550   return [appsPageShortcutButton_ isHidden];
   1551 }
   1552 
   1553 - (BookmarkButton*)otherBookmarksButton {
   1554   return otherBookmarksButton_.get();
   1555 }
   1556 
   1557 - (BookmarkBarFolderController*)folderController {
   1558   return folderController_;
   1559 }
   1560 
   1561 - (id)folderTarget {
   1562   return folderTarget_.get();
   1563 }
   1564 
   1565 - (int)displayedButtonCount {
   1566   return displayedButtonCount_;
   1567 }
   1568 
   1569 // Delete all buttons (bookmarks, chevron, "other bookmarks") from the
   1570 // bookmark bar; reset knowledge of bookmarks.
   1571 - (void)clearBookmarkBar {
   1572   for (BookmarkButton* button in buttons_.get()) {
   1573     [button setDelegate:nil];
   1574     [button removeFromSuperview];
   1575   }
   1576   [buttons_ removeAllObjects];
   1577   [self clearMenuTagMap];
   1578   displayedButtonCount_ = 0;
   1579 
   1580   // Make sure there are no stale pointers in the pasteboard.  This
   1581   // can be important if a bookmark is deleted (via bookmark sync)
   1582   // while in the middle of a drag.  The "drag completed" code
   1583   // (e.g. [BookmarkBarView performDragOperationForBookmarkButton:]) is
   1584   // careful enough to bail if there is no data found at "drop" time.
   1585   //
   1586   // Unfortunately the clearContents selector is 10.6 only.  The best
   1587   // we can do is make sure something else is present in place of the
   1588   // stale bookmark.
   1589   NSPasteboard* pboard = [NSPasteboard pasteboardWithName:NSDragPboard];
   1590   [pboard declareTypes:[NSArray arrayWithObject:NSStringPboardType] owner:self];
   1591   [pboard setString:@"" forType:NSStringPboardType];
   1592 }
   1593 
   1594 // Return an autoreleased NSCell suitable for a bookmark button.
   1595 // TODO(jrg): move much of the cell config into the BookmarkButtonCell class.
   1596 - (BookmarkButtonCell*)cellForBookmarkNode:(const BookmarkNode*)node {
   1597   NSImage* image = node ? [self faviconForNode:node] : nil;
   1598   BookmarkButtonCell* cell =
   1599       [BookmarkButtonCell buttonCellForNode:node
   1600                                        text:nil
   1601                                       image:image
   1602                              menuController:contextMenuController_];
   1603   [cell setTag:kStandardButtonTypeWithLimitedClickFeedback];
   1604 
   1605   // Note: a quirk of setting a cell's text color is that it won't work
   1606   // until the cell is associated with a button, so we can't theme the cell yet.
   1607 
   1608   return cell;
   1609 }
   1610 
   1611 // Return an autoreleased NSCell suitable for a special button displayed on the
   1612 // bookmark bar that is not attached to any bookmark node.
   1613 // TODO(jrg): move much of the cell config into the BookmarkButtonCell class.
   1614 - (BookmarkButtonCell*)cellForCustomButtonWithText:(NSString*)text
   1615                                              image:(NSImage*)image {
   1616   BookmarkButtonCell* cell =
   1617       [BookmarkButtonCell buttonCellWithText:text
   1618                                        image:image
   1619                               menuController:contextMenuController_];
   1620   [cell setTag:kStandardButtonTypeWithLimitedClickFeedback];
   1621 
   1622   // Note: a quirk of setting a cell's text color is that it won't work
   1623   // until the cell is associated with a button, so we can't theme the cell yet.
   1624 
   1625   return cell;
   1626 }
   1627 
   1628 // Returns a frame appropriate for the given bookmark cell, suitable
   1629 // for creating an NSButton that will contain it.  |xOffset| is the X
   1630 // offset for the frame; it is increased to be an appropriate X offset
   1631 // for the next button.
   1632 - (NSRect)frameForBookmarkButtonFromCell:(NSCell*)cell
   1633                                  xOffset:(int*)xOffset {
   1634   DCHECK(xOffset);
   1635   NSRect bounds = [buttonView_ bounds];
   1636   bounds.size.height = bookmarks::kBookmarkButtonHeight;
   1637 
   1638   NSRect frame = NSInsetRect(bounds,
   1639                              bookmarks::kBookmarkHorizontalPadding,
   1640                              bookmarks::kBookmarkVerticalPadding);
   1641   frame.size.width = [self widthForBookmarkButtonCell:cell];
   1642 
   1643   // Add an X offset based on what we've already done
   1644   frame.origin.x += *xOffset;
   1645 
   1646   // And up the X offset for next time.
   1647   *xOffset = NSMaxX(frame);
   1648 
   1649   return frame;
   1650 }
   1651 
   1652 // A bookmark button's contents changed.  Check for growth
   1653 // (e.g. increase the width up to the maximum).  If we grew, move
   1654 // other bookmark buttons over.
   1655 - (void)checkForBookmarkButtonGrowth:(NSButton*)changedButton {
   1656   NSRect frame = [changedButton frame];
   1657   CGFloat desiredSize = [self widthForBookmarkButtonCell:[changedButton cell]];
   1658   CGFloat delta = desiredSize - frame.size.width;
   1659   if (delta) {
   1660     frame.size.width = desiredSize;
   1661     [changedButton setFrame:frame];
   1662     for (NSButton* button in buttons_.get()) {
   1663       NSRect buttonFrame = [button frame];
   1664       if (buttonFrame.origin.x > frame.origin.x) {
   1665         buttonFrame.origin.x += delta;
   1666         [button setFrame:buttonFrame];
   1667       }
   1668     }
   1669   }
   1670   // We may have just crossed a threshold to enable the off-the-side
   1671   // button.
   1672   [self configureOffTheSideButtonContentsAndVisibility];
   1673 }
   1674 
   1675 // Called when our controlled frame has changed size.
   1676 - (void)frameDidChange {
   1677   if (!bookmarkModel_->loaded())
   1678     return;
   1679   [self updateTheme:[[[self view] window] themeProvider]];
   1680   [self reconfigureBookmarkBar];
   1681 }
   1682 
   1683 // Given a NSMenuItem tag, return the appropriate bookmark node id.
   1684 - (int64)nodeIdFromMenuTag:(int32)tag {
   1685   return menuTagMap_[tag];
   1686 }
   1687 
   1688 // Create and return a new tag for the given node id.
   1689 - (int32)menuTagFromNodeId:(int64)menuid {
   1690   int tag = seedId_++;
   1691   menuTagMap_[tag] = menuid;
   1692   return tag;
   1693 }
   1694 
   1695 // Adapt appearance of buttons to the current theme. Called after
   1696 // theme changes, or when our view is added to the view hierarchy.
   1697 // Oddly, the view pings us instead of us pinging our view.  This is
   1698 // because our trigger is an [NSView viewWillMoveToWindow:], which the
   1699 // controller doesn't normally know about.  Otherwise we don't have
   1700 // access to the theme before we know what window we will be on.
   1701 - (void)updateTheme:(ui::ThemeProvider*)themeProvider {
   1702   if (!themeProvider)
   1703     return;
   1704   NSColor* color =
   1705       themeProvider->GetNSColor(ThemeProperties::COLOR_BOOKMARK_TEXT);
   1706   for (BookmarkButton* button in buttons_.get()) {
   1707     BookmarkButtonCell* cell = [button cell];
   1708     [cell setTextColor:color];
   1709   }
   1710   [[otherBookmarksButton_ cell] setTextColor:color];
   1711   [[appsPageShortcutButton_ cell] setTextColor:color];
   1712 }
   1713 
   1714 // Return YES if the event indicates an exit from the bookmark bar
   1715 // folder menus.  E.g. "click outside" of the area we are watching.
   1716 // At this time we are watching the area that includes all popup
   1717 // bookmark folder windows.
   1718 - (BOOL)isEventAnExitEvent:(NSEvent*)event {
   1719   NSWindow* eventWindow = [event window];
   1720   NSWindow* myWindow = [[self view] window];
   1721   switch ([event type]) {
   1722     case NSLeftMouseDown:
   1723     case NSRightMouseDown:
   1724       // If the click is in my window but NOT in the bookmark bar, consider
   1725       // it a click 'outside'. Clicks directly on an active button (i.e. one
   1726       // that is a folder and for which its folder menu is showing) are 'in'.
   1727       // All other clicks on the bookmarks bar are counted as 'outside'
   1728       // because they should close any open bookmark folder menu.
   1729       if (eventWindow == myWindow) {
   1730         NSView* hitView =
   1731             [[eventWindow contentView] hitTest:[event locationInWindow]];
   1732         if (hitView == [folderController_ parentButton])
   1733           return NO;
   1734         if (![hitView isDescendantOf:[self view]] || hitView == buttonView_)
   1735           return YES;
   1736       }
   1737       // If a click in a bookmark bar folder window and that isn't
   1738       // one of my bookmark bar folders, YES is click outside.
   1739       if (![eventWindow isKindOfClass:[BookmarkBarFolderWindow
   1740                                        class]]) {
   1741         return YES;
   1742       }
   1743       break;
   1744     case NSKeyDown: {
   1745       // Event hooks often see the same keydown event twice due to the way key
   1746       // events get dispatched and redispatched, so ignore if this keydown
   1747       // event has the EXACT same timestamp as the previous keydown.
   1748       static NSTimeInterval lastKeyDownEventTime;
   1749       NSTimeInterval thisTime = [event timestamp];
   1750       if (lastKeyDownEventTime != thisTime) {
   1751         lastKeyDownEventTime = thisTime;
   1752         if ([event modifierFlags] & NSCommandKeyMask)
   1753           return YES;
   1754         else if (folderController_)
   1755           return [folderController_ handleInputText:[event characters]];
   1756       }
   1757       return NO;
   1758     }
   1759     case NSKeyUp:
   1760       return NO;
   1761     case NSLeftMouseDragged:
   1762       // We can get here with the following sequence:
   1763       // - open a bookmark folder
   1764       // - right-click (and unclick) on it to open context menu
   1765       // - move mouse to window titlebar then click-drag it by the titlebar
   1766       // http://crbug.com/49333
   1767       return NO;
   1768     default:
   1769       break;
   1770   }
   1771   return NO;
   1772 }
   1773 
   1774 #pragma mark Drag & Drop
   1775 
   1776 // Find something like std::is_between<T>?  I can't believe one doesn't exist.
   1777 static BOOL ValueInRangeInclusive(CGFloat low, CGFloat value, CGFloat high) {
   1778   return ((value >= low) && (value <= high));
   1779 }
   1780 
   1781 // Return the proposed drop target for a hover open button from the
   1782 // given array, or nil if none.  We use this for distinguishing
   1783 // between a hover-open candidate or drop-indicator draw.
   1784 // Helper for buttonForDroppingOnAtPoint:.
   1785 // Get UI review on "middle half" ness.
   1786 // http://crbug.com/36276
   1787 - (BookmarkButton*)buttonForDroppingOnAtPoint:(NSPoint)point
   1788                                     fromArray:(NSArray*)array {
   1789   for (BookmarkButton* button in array) {
   1790     // Hidden buttons can overlap valid visible buttons, just ignore.
   1791     if ([button isHidden])
   1792       continue;
   1793     // Break early if we've gone too far.
   1794     if ((NSMinX([button frame]) > point.x) || (![button superview]))
   1795       return nil;
   1796     // Careful -- this only applies to the bar with horiz buttons.
   1797     // Intentionally NOT using NSPointInRect() so that scrolling into
   1798     // a submenu doesn't cause it to be closed.
   1799     if (ValueInRangeInclusive(NSMinX([button frame]),
   1800                               point.x,
   1801                               NSMaxX([button frame]))) {
   1802       // Over a button but let's be a little more specific (make sure
   1803       // it's over the middle half, not just over it).
   1804       NSRect frame = [button frame];
   1805       NSRect middleHalfOfButton = NSInsetRect(frame, frame.size.width / 4, 0);
   1806       if (ValueInRangeInclusive(NSMinX(middleHalfOfButton),
   1807                                 point.x,
   1808                                 NSMaxX(middleHalfOfButton))) {
   1809         // It makes no sense to drop on a non-folder; there is no hover.
   1810         if (![button isFolder])
   1811           return nil;
   1812         // Got it!
   1813         return button;
   1814       } else {
   1815         // Over a button but not over the middle half.
   1816         return nil;
   1817       }
   1818     }
   1819   }
   1820   // Not hovering over a button.
   1821   return nil;
   1822 }
   1823 
   1824 // Return the proposed drop target for a hover open button, or nil if
   1825 // none.  Works with both the bookmark buttons and the "Other
   1826 // Bookmarks" button.  Point is in [self view] coordinates.
   1827 - (BookmarkButton*)buttonForDroppingOnAtPoint:(NSPoint)point {
   1828   point = [[self view] convertPoint:point
   1829                            fromView:[[[self view] window] contentView]];
   1830 
   1831   // If there's a hover button, return it if the point is within its bounds.
   1832   // Since the logic in -buttonForDroppingOnAtPoint:fromArray: only matches a
   1833   // button when the point is over the middle half, this is needed to prevent
   1834   // the button's folder being closed if the mouse temporarily leaves the
   1835   // middle half but is still within the button bounds.
   1836   if (hoverButton_ && NSPointInRect(point, [hoverButton_ frame]))
   1837      return hoverButton_.get();
   1838 
   1839   BookmarkButton* button = [self buttonForDroppingOnAtPoint:point
   1840                                                   fromArray:buttons_.get()];
   1841   // One more chance -- try "Other Bookmarks" and "off the side" (if visible).
   1842   // This is different than BookmarkBarFolderController.
   1843   if (!button) {
   1844     NSMutableArray* array = [NSMutableArray array];
   1845     if (![self offTheSideButtonIsHidden])
   1846       [array addObject:offTheSideButton_];
   1847     [array addObject:otherBookmarksButton_];
   1848     button = [self buttonForDroppingOnAtPoint:point
   1849                                     fromArray:array];
   1850   }
   1851   return button;
   1852 }
   1853 
   1854 - (int)indexForDragToPoint:(NSPoint)point {
   1855   // TODO(jrg): revisit position info based on UI team feedback.
   1856   // dropLocation is in bar local coordinates.
   1857   NSPoint dropLocation =
   1858       [[self view] convertPoint:point
   1859                        fromView:[[[self view] window] contentView]];
   1860   BookmarkButton* buttonToTheRightOfDraggedButton = nil;
   1861   for (BookmarkButton* button in buttons_.get()) {
   1862     CGFloat midpoint = NSMidX([button frame]);
   1863     if (dropLocation.x <= midpoint) {
   1864       buttonToTheRightOfDraggedButton = button;
   1865       break;
   1866     }
   1867   }
   1868   if (buttonToTheRightOfDraggedButton) {
   1869     const BookmarkNode* afterNode =
   1870         [buttonToTheRightOfDraggedButton bookmarkNode];
   1871     DCHECK(afterNode);
   1872     int index = afterNode->parent()->GetIndexOf(afterNode);
   1873     // Make sure we don't get confused by buttons which aren't visible.
   1874     return std::min(index, displayedButtonCount_);
   1875   }
   1876 
   1877   // If nothing is to my right I am at the end!
   1878   return displayedButtonCount_;
   1879 }
   1880 
   1881 // TODO(mrossetti,jrg): Yet more duplicated code.
   1882 // http://crbug.com/35966
   1883 - (BOOL)dragBookmark:(const BookmarkNode*)sourceNode
   1884                   to:(NSPoint)point
   1885                 copy:(BOOL)copy {
   1886   DCHECK(sourceNode);
   1887   // Drop destination.
   1888   const BookmarkNode* destParent = NULL;
   1889   int destIndex = 0;
   1890 
   1891   // First check if we're dropping on a button.  If we have one, and
   1892   // it's a folder, drop in it.
   1893   BookmarkButton* button = [self buttonForDroppingOnAtPoint:point];
   1894   if ([button isFolder]) {
   1895     destParent = [button bookmarkNode];
   1896     // Drop it at the end.
   1897     destIndex = [button bookmarkNode]->child_count();
   1898   } else {
   1899     // Else we're dropping somewhere on the bar, so find the right spot.
   1900     destParent = bookmarkModel_->bookmark_bar_node();
   1901     destIndex = [self indexForDragToPoint:point];
   1902   }
   1903 
   1904   // Be sure we don't try and drop a folder into itself.
   1905   if (sourceNode != destParent) {
   1906     if (copy)
   1907       bookmarkModel_->Copy(sourceNode, destParent, destIndex);
   1908     else
   1909       bookmarkModel_->Move(sourceNode, destParent, destIndex);
   1910   }
   1911 
   1912   [self closeFolderAndStopTrackingMenus];
   1913 
   1914   // Movement of a node triggers observers (like us) to rebuild the
   1915   // bar so we don't have to do so explicitly.
   1916 
   1917   return YES;
   1918 }
   1919 
   1920 - (void)draggingEnded:(id<NSDraggingInfo>)info {
   1921   [self closeFolderAndStopTrackingMenus];
   1922   [[BookmarkButton draggedButton] setHidden:NO];
   1923   [self resetAllButtonPositionsWithAnimation:YES];
   1924 }
   1925 
   1926 // Set insertionPos_ and hasInsertionPos_, and make insertion space for a
   1927 // hypothetical drop with the new button having a left edge of |where|.
   1928 // Gets called only by our view.
   1929 - (void)setDropInsertionPos:(CGFloat)where {
   1930   if (!hasInsertionPos_ || where != insertionPos_) {
   1931     insertionPos_ = where;
   1932     hasInsertionPos_ = YES;
   1933     CGFloat left = [appsPageShortcutButton_ isHidden] ?
   1934         bookmarks::kBookmarkLeftMargin :
   1935         NSMaxX([appsPageShortcutButton_ frame]) +
   1936             bookmarks::kBookmarkHorizontalPadding;
   1937     CGFloat paddingWidth = bookmarks::kDefaultBookmarkWidth;
   1938     BookmarkButton* draggedButton = [BookmarkButton draggedButton];
   1939     if (draggedButton) {
   1940       paddingWidth = std::min(bookmarks::kDefaultBookmarkWidth,
   1941                               NSWidth([draggedButton frame]));
   1942     }
   1943     // Put all the buttons where they belong, with all buttons to the right
   1944     // of the insertion point shuffling right to make space for it.
   1945     for (NSButton* button in buttons_.get()) {
   1946       // Hidden buttons get no space.
   1947       if ([button isHidden])
   1948         continue;
   1949       NSRect buttonFrame = [button frame];
   1950       buttonFrame.origin.x = left;
   1951       // Update "left" for next time around.
   1952       left += buttonFrame.size.width;
   1953       if (left > insertionPos_)
   1954         buttonFrame.origin.x += paddingWidth;
   1955       left += bookmarks::kBookmarkHorizontalPadding;
   1956       if (innerContentAnimationsEnabled_)
   1957         [[button animator] setFrame:buttonFrame];
   1958       else
   1959         [button setFrame:buttonFrame];
   1960     }
   1961   }
   1962 }
   1963 
   1964 // Put all visible bookmark bar buttons in their normal locations, either with
   1965 // or without animation according to the |animate| flag.
   1966 // This is generally useful, so is called from various places internally.
   1967 - (void)resetAllButtonPositionsWithAnimation:(BOOL)animate {
   1968 
   1969   // Position the apps bookmark if needed.
   1970   CGFloat left = bookmarks::kBookmarkLeftMargin;
   1971   if (![appsPageShortcutButton_ isHidden]) {
   1972     int xOffset =
   1973         bookmarks::kBookmarkLeftMargin - bookmarks::kBookmarkHorizontalPadding;
   1974     NSRect frame =
   1975         [self frameForBookmarkButtonFromCell:[appsPageShortcutButton_ cell]
   1976                                      xOffset:&xOffset];
   1977     [appsPageShortcutButton_ setFrame:frame];
   1978     left = xOffset + bookmarks::kBookmarkHorizontalPadding;
   1979   }
   1980   animate &= innerContentAnimationsEnabled_;
   1981 
   1982   for (NSButton* button in buttons_.get()) {
   1983     // Hidden buttons get no space.
   1984     if ([button isHidden])
   1985       continue;
   1986     NSRect buttonFrame = [button frame];
   1987     buttonFrame.origin.x = left;
   1988     left += buttonFrame.size.width + bookmarks::kBookmarkHorizontalPadding;
   1989     if (animate)
   1990       [[button animator] setFrame:buttonFrame];
   1991     else
   1992       [button setFrame:buttonFrame];
   1993   }
   1994 }
   1995 
   1996 // Clear insertion flag, remove insertion space and put all visible bookmark
   1997 // bar buttons in their normal locations.
   1998 // Gets called only by our view.
   1999 - (void)clearDropInsertionPos {
   2000   if (hasInsertionPos_) {
   2001     hasInsertionPos_ = NO;
   2002     [self resetAllButtonPositionsWithAnimation:YES];
   2003   }
   2004 }
   2005 
   2006 #pragma mark Bridge Notification Handlers
   2007 
   2008 // TODO(jrg): for now this is brute force.
   2009 - (void)loaded:(BookmarkModel*)model {
   2010   DCHECK(model == bookmarkModel_);
   2011   if (!model->loaded())
   2012     return;
   2013 
   2014   // If this is a rebuild request while we have a folder open, close it.
   2015   // TODO(mrossetti): Eliminate the need for this because it causes the folder
   2016   // menu to disappear after a cut/copy/paste/delete change.
   2017   // See: http://crbug.com/36614
   2018   if (folderController_)
   2019     [self closeAllBookmarkFolders];
   2020 
   2021   // Brute force nuke and build.
   2022   savedFrameWidth_ = NSWidth([[self view] frame]);
   2023   const BookmarkNode* node = model->bookmark_bar_node();
   2024   [self clearBookmarkBar];
   2025   [self createAppsPageShortcutButton];
   2026   [self addNodesToButtonList:node];
   2027   [self createOtherBookmarksButton];
   2028   [self updateTheme:[[[self view] window] themeProvider]];
   2029   [self positionRightSideButtons];
   2030   [self addButtonsToView];
   2031   [self configureOffTheSideButtonContentsAndVisibility];
   2032   [self reconfigureBookmarkBar];
   2033 }
   2034 
   2035 - (void)beingDeleted:(BookmarkModel*)model {
   2036   // The browser may be being torn down; little is safe to do.  As an
   2037   // example, it may not be safe to clear the pasteboard.
   2038   // http://crbug.com/38665
   2039 }
   2040 
   2041 - (void)nodeAdded:(BookmarkModel*)model
   2042            parent:(const BookmarkNode*)newParent index:(int)newIndex {
   2043   // If a context menu is open, close it.
   2044   [self cancelMenuTracking];
   2045 
   2046   const BookmarkNode* newNode = newParent->GetChild(newIndex);
   2047   id<BookmarkButtonControllerProtocol> newController =
   2048       [self controllerForNode:newParent];
   2049   [newController addButtonForNode:newNode atIndex:newIndex];
   2050   // If we go from 0 --> 1 bookmarks we may need to hide the
   2051   // "bookmarks go here" text container.
   2052   [self showOrHideNoItemContainerForNode:model->bookmark_bar_node()];
   2053   // Cope with chevron or "Other Bookmarks" buttons possibly changing state.
   2054   [self reconfigureBookmarkBar];
   2055 }
   2056 
   2057 // TODO(jrg): for now this is brute force.
   2058 - (void)nodeChanged:(BookmarkModel*)model
   2059                node:(const BookmarkNode*)node {
   2060   [self loaded:model];
   2061 }
   2062 
   2063 - (void)nodeMoved:(BookmarkModel*)model
   2064         oldParent:(const BookmarkNode*)oldParent oldIndex:(int)oldIndex
   2065         newParent:(const BookmarkNode*)newParent newIndex:(int)newIndex {
   2066   const BookmarkNode* movedNode = newParent->GetChild(newIndex);
   2067   id<BookmarkButtonControllerProtocol> oldController =
   2068       [self controllerForNode:oldParent];
   2069   id<BookmarkButtonControllerProtocol> newController =
   2070       [self controllerForNode:newParent];
   2071   if (newController == oldController) {
   2072     [oldController moveButtonFromIndex:oldIndex toIndex:newIndex];
   2073   } else {
   2074     [oldController removeButton:oldIndex animate:NO];
   2075     [newController addButtonForNode:movedNode atIndex:newIndex];
   2076   }
   2077   // If the bar is one of the parents we may need to update the visibility
   2078   // of the "bookmarks go here" presentation.
   2079   [self showOrHideNoItemContainerForNode:model->bookmark_bar_node()];
   2080   // Cope with chevron or "Other Bookmarks" buttons possibly changing state.
   2081   [self reconfigureBookmarkBar];
   2082 }
   2083 
   2084 - (void)nodeRemoved:(BookmarkModel*)model
   2085              parent:(const BookmarkNode*)oldParent index:(int)index {
   2086   // If a context menu is open, close it.
   2087   [self cancelMenuTracking];
   2088 
   2089   // Locate the parent node. The parent may not be showing, in which case
   2090   // we do nothing.
   2091   id<BookmarkButtonControllerProtocol> parentController =
   2092       [self controllerForNode:oldParent];
   2093   [parentController removeButton:index animate:YES];
   2094   // If we go from 1 --> 0 bookmarks we may need to show the
   2095   // "bookmarks go here" text container.
   2096   [self showOrHideNoItemContainerForNode:model->bookmark_bar_node()];
   2097   // If we deleted the only item on the "off the side" menu we no
   2098   // longer need to show it.
   2099   [self reconfigureBookmarkBar];
   2100 }
   2101 
   2102 // TODO(jrg): linear searching is bad.
   2103 // Need a BookmarkNode-->NSCell mapping.
   2104 //
   2105 // TODO(jrg): if the bookmark bar is open on launch, we see the
   2106 // buttons all placed, then "scooted over" as the favicons load.  If
   2107 // this looks bad I may need to change widthForBookmarkButtonCell to
   2108 // add space for an image even if not there on the assumption that
   2109 // favicons will eventually load.
   2110 - (void)nodeFaviconLoaded:(BookmarkModel*)model
   2111                      node:(const BookmarkNode*)node {
   2112   for (BookmarkButton* button in buttons_.get()) {
   2113     const BookmarkNode* cellnode = [button bookmarkNode];
   2114     if (cellnode == node) {
   2115       [[button cell] setBookmarkCellText:[button title]
   2116                                    image:[self faviconForNode:node]];
   2117       // Adding an image means we might need more room for the
   2118       // bookmark.  Test for it by growing the button (if needed)
   2119       // and shifting everything else over.
   2120       [self checkForBookmarkButtonGrowth:button];
   2121       return;
   2122     }
   2123   }
   2124 
   2125   if (folderController_)
   2126     [folderController_ faviconLoadedForNode:node];
   2127 }
   2128 
   2129 // TODO(jrg): for now this is brute force.
   2130 - (void)nodeChildrenReordered:(BookmarkModel*)model
   2131                          node:(const BookmarkNode*)node {
   2132   [self loaded:model];
   2133 }
   2134 
   2135 #pragma mark BookmarkBarState Protocol
   2136 
   2137 // (BookmarkBarState protocol)
   2138 - (BOOL)isVisible {
   2139   return barIsEnabled_ && (currentState_ == BookmarkBar::SHOW ||
   2140                            currentState_ == BookmarkBar::DETACHED ||
   2141                            lastState_ == BookmarkBar::SHOW ||
   2142                            lastState_ == BookmarkBar::DETACHED);
   2143 }
   2144 
   2145 // (BookmarkBarState protocol)
   2146 - (BOOL)isInState:(BookmarkBar::State)state {
   2147   return currentState_ == state && ![self isAnimationRunning];
   2148 }
   2149 
   2150 // (BookmarkBarState protocol)
   2151 - (BOOL)isAnimatingToState:(BookmarkBar::State)state {
   2152   return currentState_ == state && [self isAnimationRunning];
   2153 }
   2154 
   2155 // (BookmarkBarState protocol)
   2156 - (BOOL)isAnimatingFromState:(BookmarkBar::State)state {
   2157   return lastState_ == state && [self isAnimationRunning];
   2158 }
   2159 
   2160 // (BookmarkBarState protocol)
   2161 - (BOOL)isAnimatingFromState:(BookmarkBar::State)fromState
   2162                      toState:(BookmarkBar::State)toState {
   2163   return lastState_ == fromState &&
   2164          currentState_ == toState &&
   2165          [self isAnimationRunning];
   2166 }
   2167 
   2168 // (BookmarkBarState protocol)
   2169 - (BOOL)isAnimatingBetweenState:(BookmarkBar::State)fromState
   2170                        andState:(BookmarkBar::State)toState {
   2171   return [self isAnimatingFromState:fromState toState:toState] ||
   2172          [self isAnimatingFromState:toState toState:fromState];
   2173 }
   2174 
   2175 // (BookmarkBarState protocol)
   2176 - (CGFloat)detachedMorphProgress {
   2177   if ([self isInState:BookmarkBar::DETACHED]) {
   2178     return 1;
   2179   }
   2180   if ([self isAnimatingToState:BookmarkBar::DETACHED]) {
   2181     return static_cast<CGFloat>(
   2182         [[self animatableView] currentAnimationProgress]);
   2183   }
   2184   if ([self isAnimatingFromState:BookmarkBar::DETACHED]) {
   2185     return static_cast<CGFloat>(
   2186         1 - [[self animatableView] currentAnimationProgress]);
   2187   }
   2188   return 0;
   2189 }
   2190 
   2191 #pragma mark BookmarkBarToolbarViewController Protocol
   2192 
   2193 - (int)currentTabContentsHeight {
   2194   BrowserWindowController* browserController =
   2195       [BrowserWindowController browserWindowControllerForView:[self view]];
   2196   return NSHeight([[browserController tabContentArea] frame]);
   2197 }
   2198 
   2199 - (ThemeService*)themeService {
   2200   return ThemeServiceFactory::GetForProfile(browser_->profile());
   2201 }
   2202 
   2203 #pragma mark BookmarkButtonDelegate Protocol
   2204 
   2205 - (void)fillPasteboard:(NSPasteboard*)pboard
   2206        forDragOfButton:(BookmarkButton*)button {
   2207   [[self folderTarget] fillPasteboard:pboard forDragOfButton:button];
   2208 }
   2209 
   2210 // BookmarkButtonDelegate protocol implementation.  When menus are
   2211 // "active" (e.g. you clicked to open one), moving the mouse over
   2212 // another folder button should close the 1st and open the 2nd (like
   2213 // real menus).  We detect and act here.
   2214 - (void)mouseEnteredButton:(id)sender event:(NSEvent*)event {
   2215   DCHECK([sender isKindOfClass:[BookmarkButton class]]);
   2216 
   2217   // If folder menus are not being shown, do nothing.  This is different from
   2218   // BookmarkBarFolderController's implementation because the bar should NOT
   2219   // automatically open folder menus when the mouse passes over a folder
   2220   // button while the BookmarkBarFolderController DOES automatically open
   2221   // a subfolder menu.
   2222   if (!showFolderMenus_)
   2223     return;
   2224 
   2225   // From here down: same logic as BookmarkBarFolderController.
   2226   // TODO(jrg): find a way to share these 4 non-comment lines?
   2227   // http://crbug.com/35966
   2228   // If already opened, then we exited but re-entered the button, so do nothing.
   2229   if ([folderController_ parentButton] == sender)
   2230     return;
   2231   // Else open a new one if it makes sense to do so.
   2232   const BookmarkNode* node = [sender bookmarkNode];
   2233   if (node && node->is_folder()) {
   2234     // Update |hoverButton_| so that it corresponds to the open folder.
   2235     hoverButton_.reset([sender retain]);
   2236     [folderTarget_ openBookmarkFolderFromButton:sender];
   2237   } else {
   2238     // We're over a non-folder bookmark so close any old folders.
   2239     [folderController_ close];
   2240     folderController_ = nil;
   2241   }
   2242 }
   2243 
   2244 // BookmarkButtonDelegate protocol implementation.
   2245 - (void)mouseExitedButton:(id)sender event:(NSEvent*)event {
   2246   // Don't care; do nothing.
   2247   // This is different behavior that the folder menus.
   2248 }
   2249 
   2250 - (NSWindow*)browserWindow {
   2251   return [[self view] window];
   2252 }
   2253 
   2254 - (BOOL)canDragBookmarkButtonToTrash:(BookmarkButton*)button {
   2255   return [self canEditBookmarks] &&
   2256          [self canEditBookmark:[button bookmarkNode]];
   2257 }
   2258 
   2259 - (void)didDragBookmarkToTrash:(BookmarkButton*)button {
   2260   if ([self canDragBookmarkButtonToTrash:button]) {
   2261     const BookmarkNode* node = [button bookmarkNode];
   2262     if (node) {
   2263       const BookmarkNode* parent = node->parent();
   2264       bookmarkModel_->Remove(parent,
   2265                              parent->GetIndexOf(node));
   2266     }
   2267   }
   2268 }
   2269 
   2270 - (void)bookmarkDragDidEnd:(BookmarkButton*)button
   2271                  operation:(NSDragOperation)operation {
   2272   [button setHidden:NO];
   2273   [self resetAllButtonPositionsWithAnimation:YES];
   2274 }
   2275 
   2276 
   2277 #pragma mark BookmarkButtonControllerProtocol
   2278 
   2279 // Close all bookmark folders.  "Folder" here is the fake menu for
   2280 // bookmark folders, not a button context menu.
   2281 - (void)closeAllBookmarkFolders {
   2282   [self watchForExitEvent:NO];
   2283   [folderController_ close];
   2284   folderController_ = nil;
   2285 }
   2286 
   2287 - (void)closeBookmarkFolder:(id)sender {
   2288   // We're the top level, so close one means close them all.
   2289   [self closeAllBookmarkFolders];
   2290 }
   2291 
   2292 - (BookmarkModel*)bookmarkModel {
   2293   return bookmarkModel_;
   2294 }
   2295 
   2296 - (BOOL)draggingAllowed:(id<NSDraggingInfo>)info {
   2297   return [self canEditBookmarks];
   2298 }
   2299 
   2300 // TODO(jrg): much of this logic is duped with
   2301 // [BookmarkBarFolderController draggingEntered:] except when noted.
   2302 // http://crbug.com/35966
   2303 - (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)info {
   2304   NSPoint point = [info draggingLocation];
   2305   BookmarkButton* button = [self buttonForDroppingOnAtPoint:point];
   2306 
   2307   // Don't allow drops that would result in cycles.
   2308   if (button) {
   2309     NSData* data = [[info draggingPasteboard]
   2310                     dataForType:kBookmarkButtonDragType];
   2311     if (data && [info draggingSource]) {
   2312       BookmarkButton* sourceButton = nil;
   2313       [data getBytes:&sourceButton length:sizeof(sourceButton)];
   2314       const BookmarkNode* sourceNode = [sourceButton bookmarkNode];
   2315       const BookmarkNode* destNode = [button bookmarkNode];
   2316       if (destNode->HasAncestor(sourceNode))
   2317         button = nil;
   2318     }
   2319   }
   2320 
   2321   if ([button isFolder]) {
   2322     if (hoverButton_ == button) {
   2323       return NSDragOperationMove;  // already open or timed to open
   2324     }
   2325     if (hoverButton_) {
   2326       // Oops, another one triggered or open.
   2327       [NSObject cancelPreviousPerformRequestsWithTarget:[hoverButton_
   2328                                                          target]];
   2329       // Unlike BookmarkBarFolderController, we do not delay the close
   2330       // of the previous one.  Given the lack of diagonal movement,
   2331       // there is no need, and it feels awkward to do so.  See
   2332       // comments about kDragHoverCloseDelay in
   2333       // bookmark_bar_folder_controller.mm for more details.
   2334       [[hoverButton_ target] closeBookmarkFolder:hoverButton_];
   2335       hoverButton_.reset();
   2336     }
   2337     hoverButton_.reset([button retain]);
   2338     DCHECK([[hoverButton_ target]
   2339             respondsToSelector:@selector(openBookmarkFolderFromButton:)]);
   2340     [[hoverButton_ target]
   2341      performSelector:@selector(openBookmarkFolderFromButton:)
   2342      withObject:hoverButton_
   2343      afterDelay:bookmarks::kDragHoverOpenDelay
   2344      inModes:[NSArray arrayWithObject:NSRunLoopCommonModes]];
   2345   }
   2346   if (!button) {
   2347     if (hoverButton_) {
   2348       [NSObject cancelPreviousPerformRequestsWithTarget:[hoverButton_ target]];
   2349       [[hoverButton_ target] closeBookmarkFolder:hoverButton_];
   2350       hoverButton_.reset();
   2351     }
   2352   }
   2353 
   2354   // Thrown away but kept to be consistent with the draggingEntered: interface.
   2355   return NSDragOperationMove;
   2356 }
   2357 
   2358 - (void)draggingExited:(id<NSDraggingInfo>)info {
   2359   // Only close the folder menu if the user dragged up past the BMB. If the user
   2360   // dragged to below the BMB, they might be trying to drop a link into the open
   2361   // folder menu.
   2362   // TODO(asvitkine): Need a way to close the menu if the user dragged below but
   2363   //                  not into the menu.
   2364   NSRect bounds = [[self view] bounds];
   2365   NSPoint origin = [[self view] convertPoint:bounds.origin toView:nil];
   2366   if ([info draggingLocation].y > origin.y + bounds.size.height)
   2367     [self closeFolderAndStopTrackingMenus];
   2368 
   2369   // NOT the same as a cancel --> we may have moved the mouse into the submenu.
   2370   if (hoverButton_) {
   2371     [NSObject cancelPreviousPerformRequestsWithTarget:[hoverButton_ target]];
   2372     hoverButton_.reset();
   2373   }
   2374 }
   2375 
   2376 - (BOOL)dragShouldLockBarVisibility {
   2377   return ![self isInState:BookmarkBar::DETACHED] &&
   2378   ![self isAnimatingToState:BookmarkBar::DETACHED];
   2379 }
   2380 
   2381 // TODO(mrossetti,jrg): Yet more code dup with BookmarkBarFolderController.
   2382 // http://crbug.com/35966
   2383 - (BOOL)dragButton:(BookmarkButton*)sourceButton
   2384                 to:(NSPoint)point
   2385               copy:(BOOL)copy {
   2386   DCHECK([sourceButton isKindOfClass:[BookmarkButton class]]);
   2387   const BookmarkNode* sourceNode = [sourceButton bookmarkNode];
   2388   return [self dragBookmark:sourceNode to:point copy:copy];
   2389 }
   2390 
   2391 - (BOOL)dragBookmarkData:(id<NSDraggingInfo>)info {
   2392   BOOL dragged = NO;
   2393   std::vector<const BookmarkNode*> nodes([self retrieveBookmarkNodeData]);
   2394   if (nodes.size()) {
   2395     BOOL copy = !([info draggingSourceOperationMask] & NSDragOperationMove);
   2396     NSPoint dropPoint = [info draggingLocation];
   2397     for (std::vector<const BookmarkNode*>::const_iterator it = nodes.begin();
   2398          it != nodes.end(); ++it) {
   2399       const BookmarkNode* sourceNode = *it;
   2400       dragged = [self dragBookmark:sourceNode to:dropPoint copy:copy];
   2401     }
   2402   }
   2403   return dragged;
   2404 }
   2405 
   2406 - (std::vector<const BookmarkNode*>)retrieveBookmarkNodeData {
   2407   std::vector<const BookmarkNode*> dragDataNodes;
   2408   BookmarkNodeData dragData;
   2409   if (dragData.ReadFromDragClipboard()) {
   2410     std::vector<const BookmarkNode*> nodes(
   2411         dragData.GetNodes(browser_->profile()));
   2412     dragDataNodes.assign(nodes.begin(), nodes.end());
   2413   }
   2414   return dragDataNodes;
   2415 }
   2416 
   2417 // Return YES if we should show the drop indicator, else NO.
   2418 - (BOOL)shouldShowIndicatorShownForPoint:(NSPoint)point {
   2419   return ![self buttonForDroppingOnAtPoint:point];
   2420 }
   2421 
   2422 // Return the x position for a drop indicator.
   2423 - (CGFloat)indicatorPosForDragToPoint:(NSPoint)point {
   2424   CGFloat x = 0;
   2425   CGFloat halfHorizontalPadding = 0.5 * bookmarks::kBookmarkHorizontalPadding;
   2426   int destIndex = [self indexForDragToPoint:point];
   2427   int numButtons = displayedButtonCount_;
   2428 
   2429   CGFloat leftmostX;
   2430   if ([appsPageShortcutButton_ isHidden])
   2431     leftmostX = bookmarks::kBookmarkLeftMargin - halfHorizontalPadding;
   2432   else
   2433     leftmostX = NSMaxX([appsPageShortcutButton_ frame]) + halfHorizontalPadding;
   2434 
   2435   // If it's a drop strictly between existing buttons ...
   2436   if (destIndex == 0) {
   2437     x = leftmostX;
   2438   } else if (destIndex > 0 && destIndex < numButtons) {
   2439     // ... put the indicator right between the buttons.
   2440     BookmarkButton* button =
   2441         [buttons_ objectAtIndex:static_cast<NSUInteger>(destIndex-1)];
   2442     DCHECK(button);
   2443     NSRect buttonFrame = [button frame];
   2444     x = NSMaxX(buttonFrame) + halfHorizontalPadding;
   2445 
   2446     // If it's a drop at the end (past the last button, if there are any) ...
   2447   } else if (destIndex == numButtons) {
   2448     // and if it's past the last button ...
   2449     if (numButtons > 0) {
   2450       // ... find the last button, and put the indicator to its right.
   2451       BookmarkButton* button =
   2452           [buttons_ objectAtIndex:static_cast<NSUInteger>(destIndex - 1)];
   2453       DCHECK(button);
   2454       x = NSMaxX([button frame]) + halfHorizontalPadding;
   2455 
   2456       // Otherwise, put it right at the beginning.
   2457     } else {
   2458       x = leftmostX;
   2459     }
   2460   } else {
   2461     NOTREACHED();
   2462   }
   2463 
   2464   return x;
   2465 }
   2466 
   2467 - (void)childFolderWillShow:(id<BookmarkButtonControllerProtocol>)child {
   2468   // If the bookmarkbar is not in detached mode, lock bar visibility, forcing
   2469   // the overlay to stay open when in fullscreen mode.
   2470   if (![self isInState:BookmarkBar::DETACHED] &&
   2471       ![self isAnimatingToState:BookmarkBar::DETACHED]) {
   2472     BrowserWindowController* browserController =
   2473         [BrowserWindowController browserWindowControllerForView:[self view]];
   2474     [browserController lockBarVisibilityForOwner:child
   2475                                    withAnimation:NO
   2476                                            delay:NO];
   2477   }
   2478 }
   2479 
   2480 - (void)childFolderWillClose:(id<BookmarkButtonControllerProtocol>)child {
   2481   // Release bar visibility, allowing the overlay to close if in fullscreen
   2482   // mode.
   2483   BrowserWindowController* browserController =
   2484       [BrowserWindowController browserWindowControllerForView:[self view]];
   2485   [browserController releaseBarVisibilityForOwner:child
   2486                                     withAnimation:NO
   2487                                             delay:NO];
   2488 }
   2489 
   2490 // Add a new folder controller as triggered by the given folder button.
   2491 - (void)addNewFolderControllerWithParentButton:(BookmarkButton*)parentButton {
   2492 
   2493   // If doing a close/open, make sure the fullscreen chrome doesn't
   2494   // have a chance to begin animating away in the middle of things.
   2495   BrowserWindowController* browserController =
   2496       [BrowserWindowController browserWindowControllerForView:[self view]];
   2497   // Confirm we're not re-locking with ourself as an owner before locking.
   2498   DCHECK([browserController isBarVisibilityLockedForOwner:self] == NO);
   2499   [browserController lockBarVisibilityForOwner:self
   2500                                  withAnimation:NO
   2501                                          delay:NO];
   2502 
   2503   if (folderController_)
   2504     [self closeAllBookmarkFolders];
   2505 
   2506   // Folder controller, like many window controllers, owns itself.
   2507   folderController_ =
   2508       [[BookmarkBarFolderController alloc]
   2509           initWithParentButton:parentButton
   2510               parentController:nil
   2511                  barController:self
   2512                        profile:browser_->profile()];
   2513   [folderController_ showWindow:self];
   2514 
   2515   // Only BookmarkBarController has this; the
   2516   // BookmarkBarFolderController does not.
   2517   [self watchForExitEvent:YES];
   2518 
   2519   // No longer need to hold the lock; the folderController_ now owns it.
   2520   [browserController releaseBarVisibilityForOwner:self
   2521                                     withAnimation:NO
   2522                                             delay:NO];
   2523 }
   2524 
   2525 - (void)openAll:(const BookmarkNode*)node
   2526     disposition:(WindowOpenDisposition)disposition {
   2527   [self closeFolderAndStopTrackingMenus];
   2528   chrome::OpenAll([[self view] window], browser_, node, disposition,
   2529                   browser_->profile());
   2530 }
   2531 
   2532 - (void)addButtonForNode:(const BookmarkNode*)node
   2533                  atIndex:(NSInteger)buttonIndex {
   2534   int newOffset =
   2535       bookmarks::kBookmarkLeftMargin - bookmarks::kBookmarkHorizontalPadding;
   2536   if (buttonIndex == -1)
   2537     buttonIndex = [buttons_ count];  // New button goes at the end.
   2538   if (buttonIndex <= (NSInteger)[buttons_ count]) {
   2539     if (buttonIndex) {
   2540       BookmarkButton* targetButton = [buttons_ objectAtIndex:buttonIndex - 1];
   2541       NSRect targetFrame = [targetButton frame];
   2542       newOffset = targetFrame.origin.x + NSWidth(targetFrame) +
   2543           bookmarks::kBookmarkHorizontalPadding;
   2544     }
   2545     BookmarkButton* newButton = [self buttonForNode:node xOffset:&newOffset];
   2546     ++displayedButtonCount_;
   2547     [buttons_ insertObject:newButton atIndex:buttonIndex];
   2548     [buttonView_ addSubview:newButton];
   2549     [self resetAllButtonPositionsWithAnimation:NO];
   2550     // See if any buttons need to be pushed off to or brought in from the side.
   2551     [self reconfigureBookmarkBar];
   2552   } else  {
   2553     // A button from somewhere else (not the bar) is being moved to the
   2554     // off-the-side so insure it gets redrawn if its showing.
   2555     [self reconfigureBookmarkBar];
   2556     [folderController_ reconfigureMenu];
   2557   }
   2558 }
   2559 
   2560 // TODO(mrossetti): Duplicate code with BookmarkBarFolderController.
   2561 // http://crbug.com/35966
   2562 - (BOOL)addURLs:(NSArray*)urls withTitles:(NSArray*)titles at:(NSPoint)point {
   2563   DCHECK([urls count] == [titles count]);
   2564   BOOL nodesWereAdded = NO;
   2565   // Figure out where these new bookmarks nodes are to be added.
   2566   BookmarkButton* button = [self buttonForDroppingOnAtPoint:point];
   2567   const BookmarkNode* destParent = NULL;
   2568   int destIndex = 0;
   2569   if ([button isFolder]) {
   2570     destParent = [button bookmarkNode];
   2571     // Drop it at the end.
   2572     destIndex = [button bookmarkNode]->child_count();
   2573   } else {
   2574     // Else we're dropping somewhere on the bar, so find the right spot.
   2575     destParent = bookmarkModel_->bookmark_bar_node();
   2576     destIndex = [self indexForDragToPoint:point];
   2577   }
   2578 
   2579   // Don't add the bookmarks if the destination index shows an error.
   2580   if (destIndex >= 0) {
   2581     // Create and add the new bookmark nodes.
   2582     size_t urlCount = [urls count];
   2583     for (size_t i = 0; i < urlCount; ++i) {
   2584       GURL gurl;
   2585       const char* string = [[urls objectAtIndex:i] UTF8String];
   2586       if (string)
   2587         gurl = GURL(string);
   2588       // We only expect to receive valid URLs.
   2589       DCHECK(gurl.is_valid());
   2590       if (gurl.is_valid()) {
   2591         bookmarkModel_->AddURL(destParent,
   2592                                destIndex++,
   2593                                base::SysNSStringToUTF16(
   2594                                   [titles objectAtIndex:i]),
   2595                                gurl);
   2596         nodesWereAdded = YES;
   2597       }
   2598     }
   2599   }
   2600   return nodesWereAdded;
   2601 }
   2602 
   2603 - (void)moveButtonFromIndex:(NSInteger)fromIndex toIndex:(NSInteger)toIndex {
   2604   if (fromIndex != toIndex) {
   2605     NSInteger buttonCount = (NSInteger)[buttons_ count];
   2606     if (toIndex == -1)
   2607       toIndex = buttonCount;
   2608     // See if we have a simple move within the bar, which will be the case if
   2609     // both button indexes are in the visible space.
   2610     if (fromIndex < buttonCount && toIndex < buttonCount) {
   2611       BookmarkButton* movedButton = [buttons_ objectAtIndex:fromIndex];
   2612       [buttons_ removeObjectAtIndex:fromIndex];
   2613       [buttons_ insertObject:movedButton atIndex:toIndex];
   2614       [movedButton setHidden:NO];
   2615       [self resetAllButtonPositionsWithAnimation:NO];
   2616     } else if (fromIndex < buttonCount) {
   2617       // A button is being removed from the bar and added to off-the-side.
   2618       // By now the node has already been inserted into the model so the
   2619       // button to be added is represented by |toIndex|. Things get
   2620       // complicated because the off-the-side is showing and must be redrawn
   2621       // while possibly re-laying out the bookmark bar.
   2622       [self removeButton:fromIndex animate:NO];
   2623       [self reconfigureBookmarkBar];
   2624       [folderController_ reconfigureMenu];
   2625     } else if (toIndex < buttonCount) {
   2626       // A button is being added to the bar and removed from off-the-side.
   2627       // By now the node has already been inserted into the model so the
   2628       // button to be added is represented by |toIndex|.
   2629       const BookmarkNode* node = bookmarkModel_->bookmark_bar_node();
   2630       const BookmarkNode* movedNode = node->GetChild(toIndex);
   2631       DCHECK(movedNode);
   2632       [self addButtonForNode:movedNode atIndex:toIndex];
   2633       [self reconfigureBookmarkBar];
   2634     } else {
   2635       // A button is being moved within the off-the-side.
   2636       fromIndex -= buttonCount;
   2637       toIndex -= buttonCount;
   2638       [folderController_ moveButtonFromIndex:fromIndex toIndex:toIndex];
   2639     }
   2640   }
   2641 }
   2642 
   2643 - (void)removeButton:(NSInteger)buttonIndex animate:(BOOL)animate {
   2644   if (buttonIndex < (NSInteger)[buttons_ count]) {
   2645     // The button being removed is showing in the bar.
   2646     BookmarkButton* oldButton = [buttons_ objectAtIndex:buttonIndex];
   2647     if (oldButton == [folderController_ parentButton]) {
   2648       // If we are deleting a button whose folder is currently open, close it!
   2649       [self closeAllBookmarkFolders];
   2650     }
   2651     if (animate && innerContentAnimationsEnabled_ && [self isVisible] &&
   2652         [[self browserWindow] isMainWindow]) {
   2653       NSPoint poofPoint = [oldButton screenLocationForRemoveAnimation];
   2654       NSShowAnimationEffect(NSAnimationEffectDisappearingItemDefault, poofPoint,
   2655                             NSZeroSize, nil, nil, nil);
   2656     }
   2657     [oldButton setDelegate:nil];
   2658     [oldButton removeFromSuperview];
   2659     [buttons_ removeObjectAtIndex:buttonIndex];
   2660     --displayedButtonCount_;
   2661     [self resetAllButtonPositionsWithAnimation:YES];
   2662     [self reconfigureBookmarkBar];
   2663   } else if (folderController_ &&
   2664              [folderController_ parentButton] == offTheSideButton_) {
   2665     // The button being removed is in the OTS (off-the-side) and the OTS
   2666     // menu is showing so we need to remove the button.
   2667     NSInteger index = buttonIndex - displayedButtonCount_;
   2668     [folderController_ removeButton:index animate:YES];
   2669   }
   2670 }
   2671 
   2672 - (id<BookmarkButtonControllerProtocol>)controllerForNode:
   2673     (const BookmarkNode*)node {
   2674   // See if it's in the bar, then if it is in the hierarchy of visible
   2675   // folder menus.
   2676   if (bookmarkModel_->bookmark_bar_node() == node)
   2677     return self;
   2678   return [folderController_ controllerForNode:node];
   2679 }
   2680 
   2681 @end
   2682