Home | History | Annotate | Download | only in bookmarks
      1 // Copyright (c) 2011 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_folder_controller.h"
      6 
      7 #include "base/mac/mac_util.h"
      8 #include "base/sys_string_conversions.h"
      9 #include "chrome/browser/bookmarks/bookmark_model.h"
     10 #include "chrome/browser/bookmarks/bookmark_utils.h"
     11 #import "chrome/browser/ui/cocoa/bookmarks/bookmark_bar_constants.h"
     12 #import "chrome/browser/ui/cocoa/bookmarks/bookmark_bar_controller.h"
     13 #import "chrome/browser/ui/cocoa/bookmarks/bookmark_bar_folder_button_cell.h"
     14 #import "chrome/browser/ui/cocoa/bookmarks/bookmark_bar_folder_hover_state.h"
     15 #import "chrome/browser/ui/cocoa/bookmarks/bookmark_bar_folder_view.h"
     16 #import "chrome/browser/ui/cocoa/bookmarks/bookmark_bar_folder_window.h"
     17 #import "chrome/browser/ui/cocoa/bookmarks/bookmark_folder_target.h"
     18 #import "chrome/browser/ui/cocoa/browser_window_controller.h"
     19 #import "chrome/browser/ui/cocoa/event_utils.h"
     20 #include "ui/base/theme_provider.h"
     21 
     22 using bookmarks::kBookmarkBarMenuCornerRadius;
     23 
     24 namespace {
     25 
     26 // Frequency of the scrolling timer in seconds.
     27 const NSTimeInterval kBookmarkBarFolderScrollInterval = 0.1;
     28 
     29 // Amount to scroll by per timer fire.  We scroll rather slowly; to
     30 // accomodate we do several at a time.
     31 const CGFloat kBookmarkBarFolderScrollAmount =
     32     3 * bookmarks::kBookmarkFolderButtonHeight;
     33 
     34 // Amount to scroll for each scroll wheel roll.
     35 const CGFloat kBookmarkBarFolderScrollWheelAmount =
     36     1 * bookmarks::kBookmarkFolderButtonHeight;
     37 
     38 // Determining adjustments to the layout of the folder menu window in response
     39 // to resizing and scrolling relies on many visual factors. The following
     40 // struct is used to pass around these factors to the several support
     41 // functions involved in the adjustment calculations and application.
     42 struct LayoutMetrics {
     43   // Metrics applied during the final layout adjustments to the window,
     44   // the main visible content view, and the menu content view (i.e. the
     45   // scroll view).
     46   CGFloat windowLeft;
     47   NSSize windowSize;
     48   // The proposed and then final scrolling adjustment made to the scrollable
     49   // area of the folder menu. This may be modified during the window layout
     50   // primarily as a result of hiding or showing the scroll arrows.
     51   CGFloat scrollDelta;
     52   NSRect windowFrame;
     53   NSRect visibleFrame;
     54   NSRect scrollerFrame;
     55   NSPoint scrollPoint;
     56   // The difference between 'could' and 'can' in these next four data members
     57   // is this: 'could' represents the previous condition for scrollability
     58   // while 'can' represents what the new condition will be for scrollability.
     59   BOOL couldScrollUp;
     60   BOOL canScrollUp;
     61   BOOL couldScrollDown;
     62   BOOL canScrollDown;
     63   // Determines the optimal time during folder menu layout when the contents
     64   // of the button scroll area should be scrolled in order to prevent
     65   // flickering.
     66   BOOL preScroll;
     67 
     68   // Intermediate metrics used in determining window vertical layout changes.
     69   CGFloat deltaWindowHeight;
     70   CGFloat deltaWindowY;
     71   CGFloat deltaVisibleHeight;
     72   CGFloat deltaVisibleY;
     73   CGFloat deltaScrollerHeight;
     74   CGFloat deltaScrollerY;
     75 
     76   // Convenience metrics used in multiple functions (carried along here in
     77   // order to eliminate the need to calculate in multiple places and
     78   // reduce the possibility of bugs).
     79   CGFloat minimumY;
     80   CGFloat oldWindowY;
     81   CGFloat folderY;
     82   CGFloat folderTop;
     83 
     84   LayoutMetrics(CGFloat windowLeft, NSSize windowSize, CGFloat scrollDelta) :
     85     windowLeft(windowLeft),
     86     windowSize(windowSize),
     87     scrollDelta(scrollDelta),
     88     couldScrollUp(NO),
     89     canScrollUp(NO),
     90     couldScrollDown(NO),
     91     canScrollDown(NO),
     92     preScroll(NO),
     93     deltaWindowHeight(0.0),
     94     deltaWindowY(0.0),
     95     deltaVisibleHeight(0.0),
     96     deltaVisibleY(0.0),
     97     deltaScrollerHeight(0.0),
     98     deltaScrollerY(0.0),
     99     oldWindowY(0.0),
    100     folderY(0.0),
    101     folderTop(0.0) {}
    102 };
    103 
    104 }  // namespace
    105 
    106 
    107 // Required to set the right tracking bounds for our fake menus.
    108 @interface NSView(Private)
    109 - (void)_updateTrackingAreas;
    110 @end
    111 
    112 @interface BookmarkBarFolderController(Private)
    113 - (void)configureWindow;
    114 - (void)addOrUpdateScrollTracking;
    115 - (void)removeScrollTracking;
    116 - (void)endScroll;
    117 - (void)addScrollTimerWithDelta:(CGFloat)delta;
    118 
    119 // Helper function to configureWindow which performs a basic layout of
    120 // the window subviews, in particular the menu buttons and the window width.
    121 - (void)layOutWindowWithHeight:(CGFloat)height;
    122 
    123 // Determine the best button width (which will be the widest button or the
    124 // maximum allowable button width, whichever is less) and resize all buttons.
    125 // Return the new width so that the window can be adjusted.
    126 - (CGFloat)adjustButtonWidths;
    127 
    128 // Returns the total menu height needed to display |buttonCount| buttons.
    129 // Does not do any fancy tricks like trimming the height to fit on the screen.
    130 - (int)menuHeightForButtonCount:(int)buttonCount;
    131 
    132 // Adjust layout of the folder menu window components, showing/hiding the
    133 // scroll up/down arrows, and resizing as necessary for a proper disaplay.
    134 // In order to reduce window flicker, all layout changes are deferred until
    135 // the final step of the adjustment. To accommodate this deferral, window
    136 // height and width changes needed by callers to this function pass their
    137 // desired window changes in |size|. When scrolling is to be performed
    138 // any scrolling change is given by |scrollDelta|. The ultimate amount of
    139 // scrolling may be different from |scrollDelta| in order to accommodate
    140 // changes in the scroller view layout. These proposed window adjustments
    141 // are passed to helper functions using a LayoutMetrics structure.
    142 //
    143 // This function should be called when: 1) initially setting up a folder menu
    144 // window, 2) responding to scrolling of the contents (which may affect the
    145 // height of the window), 3) addition or removal of bookmark items (such as
    146 // during cut/paste/delete/drag/drop operations).
    147 - (void)adjustWindowLeft:(CGFloat)windowLeft
    148                     size:(NSSize)windowSize
    149              scrollingBy:(CGFloat)scrollDelta;
    150 
    151 // Support function for adjustWindowLeft:size:scrollingBy: which initializes
    152 // the layout adjustments by gathering current folder menu window and subviews
    153 // positions and sizes. This information is set in the |layoutMetrics|
    154 // structure.
    155 - (void)gatherMetrics:(LayoutMetrics*)layoutMetrics;
    156 
    157 // Support function for adjustWindowLeft:size:scrollingBy: which calculates
    158 // the changes which must be applied to the folder menu window and subviews
    159 // positions and sizes. |layoutMetrics| contains the proposed window size
    160 // and scrolling along with the other current window and subview layout
    161 // information. The values in |layoutMetrics| are then adjusted to
    162 // accommodate scroll arrow presentation and window growth.
    163 - (void)adjustMetrics:(LayoutMetrics*)layoutMetrics;
    164 
    165 // Support function for adjustMetrics: which calculates the layout changes
    166 // required to accommodate changes in the position and scrollability
    167 // of the top of the folder menu window.
    168 - (void)adjustMetricsForMenuTopChanges:(LayoutMetrics*)layoutMetrics;
    169 
    170 // Support function for adjustMetrics: which calculates the layout changes
    171 // required to accommodate changes in the position and scrollability
    172 // of the bottom of the folder menu window.
    173 - (void)adjustMetricsForMenuBottomChanges:(LayoutMetrics*)layoutMetrics;
    174 
    175 // Support function for adjustWindowLeft:size:scrollingBy: which applies
    176 // the layout adjustments to the folder menu window and subviews.
    177 - (void)applyMetrics:(LayoutMetrics*)layoutMetrics;
    178 
    179 // This function is called when buttons are added or removed from the folder
    180 // menu, and which may require a change in the layout of the folder menu
    181 // window. Such layout changes may include horizontal placement, width,
    182 // height, and scroller visibility changes. (This function calls through
    183 // to -[adjustWindowLeft:size:scrollingBy:].)
    184 // |buttonCount| should contain the updated count of menu buttons.
    185 - (void)adjustWindowForButtonCount:(NSUInteger)buttonCount;
    186 
    187 // A helper function which takes the desired amount to scroll, given by
    188 // |scrollDelta|, and calculates the actual scrolling change to be applied
    189 // taking into account the layout of the folder menu window and any
    190 // changes in it's scrollability. (For example, when scrolling down and the
    191 // top-most menu item is coming into view we will only scroll enough for
    192 // that item to be completely presented, which may be less than the
    193 // scroll amount requested.)
    194 - (CGFloat)determineFinalScrollDelta:(CGFloat)scrollDelta;
    195 
    196 // |point| is in the base coordinate system of the destination window;
    197 // it comes from an id<NSDraggingInfo>. |copy| is YES if a copy is to be
    198 // made and inserted into the new location while leaving the bookmark in
    199 // the old location, otherwise move the bookmark by removing from its old
    200 // location and inserting into the new location.
    201 - (BOOL)dragBookmark:(const BookmarkNode*)sourceNode
    202                   to:(NSPoint)point
    203                 copy:(BOOL)copy;
    204 
    205 @end
    206 
    207 @interface BookmarkButton (BookmarkBarFolderMenuHighlighting)
    208 
    209 // Make the button's border frame always appear when |forceOn| is YES,
    210 // otherwise only border the button when the mouse is inside the button.
    211 - (void)forceButtonBorderToStayOnAlways:(BOOL)forceOn;
    212 
    213 @end
    214 
    215 @implementation BookmarkButton (BookmarkBarFolderMenuHighlighting)
    216 
    217 - (void)forceButtonBorderToStayOnAlways:(BOOL)forceOn {
    218   [self setShowsBorderOnlyWhileMouseInside:!forceOn];
    219   [self setNeedsDisplay];
    220 }
    221 
    222 @end
    223 
    224 @implementation BookmarkBarFolderController
    225 
    226 @synthesize subFolderGrowthToRight = subFolderGrowthToRight_;
    227 
    228 - (id)initWithParentButton:(BookmarkButton*)button
    229           parentController:(BookmarkBarFolderController*)parentController
    230              barController:(BookmarkBarController*)barController {
    231   NSString* nibPath =
    232       [base::mac::MainAppBundle() pathForResource:@"BookmarkBarFolderWindow"
    233                                           ofType:@"nib"];
    234   if ((self = [super initWithWindowNibPath:nibPath owner:self])) {
    235     parentButton_.reset([button retain]);
    236     selectedIndex_ = -1;
    237 
    238     // We want the button to remain bordered as part of the menu path.
    239     [button forceButtonBorderToStayOnAlways:YES];
    240 
    241     parentController_.reset([parentController retain]);
    242     if (!parentController_)
    243       [self setSubFolderGrowthToRight:YES];
    244     else
    245       [self setSubFolderGrowthToRight:[parentController
    246                                         subFolderGrowthToRight]];
    247     barController_ = barController;  // WEAK
    248     buttons_.reset([[NSMutableArray alloc] init]);
    249     folderTarget_.reset([[BookmarkFolderTarget alloc] initWithController:self]);
    250     [self configureWindow];
    251     hoverState_.reset([[BookmarkBarFolderHoverState alloc] init]);
    252   }
    253   return self;
    254 }
    255 
    256 - (void)dealloc {
    257   [self clearInputText];
    258 
    259   // The button is no longer part of the menu path.
    260   [parentButton_ forceButtonBorderToStayOnAlways:NO];
    261   [parentButton_ setNeedsDisplay];
    262 
    263   [self removeScrollTracking];
    264   [self endScroll];
    265   [hoverState_ draggingExited];
    266 
    267   // Delegate pattern does not retain; make sure pointers to us are removed.
    268   for (BookmarkButton* button in buttons_.get()) {
    269     [button setDelegate:nil];
    270     [button setTarget:nil];
    271     [button setAction:nil];
    272   }
    273 
    274   // Note: we don't need to
    275   //   [NSObject cancelPreviousPerformRequestsWithTarget:self];
    276   // Because all of our performSelector: calls use withDelay: which
    277   // retains us.
    278   [super dealloc];
    279 }
    280 
    281 - (void)awakeFromNib {
    282   NSRect windowFrame = [[self window] frame];
    283   NSRect scrollViewFrame = [scrollView_ frame];
    284   padding_ = NSWidth(windowFrame) - NSWidth(scrollViewFrame);
    285   verticalScrollArrowHeight_ = NSHeight([scrollUpArrowView_ frame]);
    286 }
    287 
    288 // Overriden from NSWindowController to call childFolderWillShow: before showing
    289 // the window.
    290 - (void)showWindow:(id)sender {
    291   [barController_ childFolderWillShow:self];
    292   [super showWindow:sender];
    293 }
    294 
    295 - (int)buttonCount {
    296   return [[self buttons] count];
    297 }
    298 
    299 - (BookmarkButton*)parentButton {
    300   return parentButton_.get();
    301 }
    302 
    303 - (void)offsetFolderMenuWindow:(NSSize)offset {
    304   NSWindow* window = [self window];
    305   NSRect windowFrame = [window frame];
    306   windowFrame.origin.x -= offset.width;
    307   windowFrame.origin.y += offset.height;  // Yes, in the opposite direction!
    308   [window setFrame:windowFrame display:YES];
    309   [folderController_ offsetFolderMenuWindow:offset];
    310 }
    311 
    312 - (void)reconfigureMenu {
    313   [NSObject cancelPreviousPerformRequestsWithTarget:self];
    314   for (BookmarkButton* button in buttons_.get()) {
    315     [button setDelegate:nil];
    316     [button removeFromSuperview];
    317   }
    318   [buttons_ removeAllObjects];
    319   [self configureWindow];
    320 }
    321 
    322 #pragma mark Private Methods
    323 
    324 - (BookmarkButtonCell*)cellForBookmarkNode:(const BookmarkNode*)child {
    325   NSImage* image = child ? [barController_ faviconForNode:child] : nil;
    326   NSMenu* menu = child ? child->is_folder() ? folderMenu_ : buttonMenu_ : nil;
    327   BookmarkBarFolderButtonCell* cell =
    328       [BookmarkBarFolderButtonCell buttonCellForNode:child
    329                                          contextMenu:menu
    330                                             cellText:nil
    331                                            cellImage:image];
    332   [cell setTag:kStandardButtonTypeWithLimitedClickFeedback];
    333   return cell;
    334 }
    335 
    336 // Redirect to our logic shared with BookmarkBarController.
    337 - (IBAction)openBookmarkFolderFromButton:(id)sender {
    338   [folderTarget_ openBookmarkFolderFromButton:sender];
    339 }
    340 
    341 // Create a bookmark button for the given node using frame.
    342 //
    343 // If |node| is NULL this is an "(empty)" button.
    344 // Does NOT add this button to our button list.
    345 // Returns an autoreleased button.
    346 // Adjusts the input frame width as appropriate.
    347 //
    348 // TODO(jrg): combine with addNodesToButtonList: code from
    349 // bookmark_bar_controller.mm, and generalize that to use both x and y
    350 // offsets.
    351 // http://crbug.com/35966
    352 - (BookmarkButton*)makeButtonForNode:(const BookmarkNode*)node
    353                                frame:(NSRect)frame {
    354   BookmarkButtonCell* cell = [self cellForBookmarkNode:node];
    355   DCHECK(cell);
    356 
    357   // We must decide if we draw the folder arrow before we ask the cell
    358   // how big it needs to be.
    359   if (node && node->is_folder()) {
    360     // Warning when combining code with bookmark_bar_controller.mm:
    361     // this call should NOT be made for the bar buttons; only for the
    362     // subfolder buttons.
    363     [cell setDrawFolderArrow:YES];
    364   }
    365 
    366   // The "+2" is needed because, sometimes, Cocoa is off by a tad when
    367   // returning the value it thinks it needs.
    368   CGFloat desired = [cell cellSize].width + 2;
    369   // The width is determined from the maximum of the proposed width
    370   // (provided in |frame|) or the natural width of the title, then
    371   // limited by the abolute minimum and maximum allowable widths.
    372   frame.size.width =
    373       std::min(std::max(bookmarks::kBookmarkMenuButtonMinimumWidth,
    374                         std::max(frame.size.width, desired)),
    375                bookmarks::kBookmarkMenuButtonMaximumWidth);
    376 
    377   BookmarkButton* button = [[[BookmarkButton alloc] initWithFrame:frame]
    378                                autorelease];
    379   DCHECK(button);
    380 
    381   [button setCell:cell];
    382   [button setDelegate:self];
    383   if (node) {
    384     if (node->is_folder()) {
    385       [button setTarget:self];
    386       [button setAction:@selector(openBookmarkFolderFromButton:)];
    387     } else {
    388       // Make the button do something.
    389       [button setTarget:self];
    390       [button setAction:@selector(openBookmark:)];
    391       // Add a tooltip.
    392       NSString* title = base::SysUTF16ToNSString(node->GetTitle());
    393       std::string urlString = node->GetURL().possibly_invalid_spec();
    394       NSString* tooltip = [NSString stringWithFormat:@"%@\n%s", title,
    395                                     urlString.c_str()];
    396       [button setToolTip:tooltip];
    397       [button setAcceptsTrackIn:YES];
    398     }
    399   } else {
    400     [button setEnabled:NO];
    401     [button setBordered:NO];
    402   }
    403   return button;
    404 }
    405 
    406 - (id)folderTarget {
    407   return folderTarget_.get();
    408 }
    409 
    410 
    411 // Our parent controller is another BookmarkBarFolderController, so
    412 // our window is to the right or left of it.  We use a little overlap
    413 // since it looks much more menu-like than with none.  If we would
    414 // grow off the screen, switch growth to the other direction.  Growth
    415 // direction sticks for folder windows which are descendents of us.
    416 // If we have tried both directions and neither fits, degrade to a
    417 // default.
    418 - (CGFloat)childFolderWindowLeftForWidth:(int)windowWidth {
    419   // We may legitimately need to try two times (growth to right and
    420   // left but not in that order).  Limit us to three tries in case
    421   // the folder window can't fit on either side of the screen; we
    422   // don't want to loop forever.
    423   CGFloat x;
    424   int tries = 0;
    425   while (tries < 2) {
    426     // Try to grow right.
    427     if ([self subFolderGrowthToRight]) {
    428       tries++;
    429       x = NSMaxX([[parentButton_ window] frame]) -
    430           bookmarks::kBookmarkMenuOverlap;
    431       // If off the screen, switch direction.
    432       if ((x + windowWidth +
    433            bookmarks::kBookmarkHorizontalScreenPadding) >
    434           NSMaxX([[[self window] screen] visibleFrame])) {
    435         [self setSubFolderGrowthToRight:NO];
    436       } else {
    437         return x;
    438       }
    439     }
    440     // Try to grow left.
    441     if (![self subFolderGrowthToRight]) {
    442       tries++;
    443       x = NSMinX([[parentButton_ window] frame]) +
    444           bookmarks::kBookmarkMenuOverlap -
    445           windowWidth;
    446       // If off the screen, switch direction.
    447       if (x < NSMinX([[[self window] screen] visibleFrame])) {
    448         [self setSubFolderGrowthToRight:YES];
    449       } else {
    450         return x;
    451       }
    452     }
    453   }
    454   // Unhappy; do the best we can.
    455   return NSMaxX([[[self window] screen] visibleFrame]) - windowWidth;
    456 }
    457 
    458 
    459 // Compute and return the top left point of our window (screen
    460 // coordinates).  The top left is positioned in a manner similar to
    461 // cascading menus.  Windows may grow to either the right or left of
    462 // their parent (if a sub-folder) so we need to know |windowWidth|.
    463 - (NSPoint)windowTopLeftForWidth:(int)windowWidth height:(int)windowHeight {
    464   CGFloat kMinSqueezedMenuHeight = bookmarks::kBookmarkFolderButtonHeight * 2.0;
    465   NSPoint newWindowTopLeft;
    466   if (![parentController_ isKindOfClass:[self class]]) {
    467     // If we're not popping up from one of ourselves, we must be
    468     // popping up from the bookmark bar itself.  In this case, start
    469     // BELOW the parent button.  Our left is the button left; our top
    470     // is bottom of button's parent view.
    471     NSPoint buttonBottomLeftInScreen =
    472         [[parentButton_ window]
    473             convertBaseToScreen:[parentButton_
    474                                     convertPoint:NSZeroPoint toView:nil]];
    475     NSPoint bookmarkBarBottomLeftInScreen =
    476         [[parentButton_ window]
    477             convertBaseToScreen:[[parentButton_ superview]
    478                                     convertPoint:NSZeroPoint toView:nil]];
    479     newWindowTopLeft = NSMakePoint(
    480         buttonBottomLeftInScreen.x + bookmarks::kBookmarkBarButtonOffset,
    481         bookmarkBarBottomLeftInScreen.y + bookmarks::kBookmarkBarMenuOffset);
    482     // Make sure the window is on-screen; if not, push left.  It is
    483     // intentional that top level folders "push left" slightly
    484     // different than subfolders.
    485     NSRect screenFrame = [[[parentButton_ window] screen] visibleFrame];
    486     CGFloat spillOff = (newWindowTopLeft.x + windowWidth) - NSMaxX(screenFrame);
    487     if (spillOff > 0.0) {
    488       newWindowTopLeft.x = std::max(newWindowTopLeft.x - spillOff,
    489                                     NSMinX(screenFrame));
    490     }
    491     // The menu looks bad when it is squeezed up against the bottom of the
    492     // screen and ends up being only a few pixels tall. If it meets the
    493     // threshold for this case, instead show the menu above the button.
    494     NSRect visFrame = [[[parentButton_ window] screen] visibleFrame];
    495     CGFloat availableVerticalSpace = newWindowTopLeft.y -
    496         (NSMinY(visFrame) + bookmarks::kScrollWindowVerticalMargin);
    497     if ((availableVerticalSpace < kMinSqueezedMenuHeight) &&
    498         (windowHeight > availableVerticalSpace)) {
    499       newWindowTopLeft.y = std::min(
    500           newWindowTopLeft.y + windowHeight + NSHeight([parentButton_ frame]),
    501           NSMaxY(visFrame));
    502     }
    503   } else {
    504     // Parent is a folder: expose as much as we can vertically; grow right/left.
    505     newWindowTopLeft.x = [self childFolderWindowLeftForWidth:windowWidth];
    506     NSPoint topOfWindow = NSMakePoint(0,
    507                                       NSMaxY([parentButton_ frame]) -
    508                                           bookmarks::kBookmarkVerticalPadding);
    509     topOfWindow = [[parentButton_ window]
    510                    convertBaseToScreen:[[parentButton_ superview]
    511                                         convertPoint:topOfWindow toView:nil]];
    512     newWindowTopLeft.y = topOfWindow.y;
    513   }
    514   return newWindowTopLeft;
    515 }
    516 
    517 // Set our window level to the right spot so we're above the menubar, dock, etc.
    518 // Factored out so we can override/noop in a unit test.
    519 - (void)configureWindowLevel {
    520   [[self window] setLevel:NSPopUpMenuWindowLevel];
    521 }
    522 
    523 - (int)menuHeightForButtonCount:(int)buttonCount {
    524   // This does not take into account any padding which may be required at the
    525   // top and/or bottom of the window.
    526   return (buttonCount * bookmarks::kBookmarkFolderButtonHeight) +
    527       2 * bookmarks::kBookmarkVerticalPadding;
    528 }
    529 
    530 - (void)adjustWindowLeft:(CGFloat)windowLeft
    531                     size:(NSSize)windowSize
    532              scrollingBy:(CGFloat)scrollDelta {
    533   // Callers of this function should make adjustments to the vertical
    534   // attributes of the folder view only (height, scroll position).
    535   // This function will then make appropriate layout adjustments in order
    536   // to accommodate screen/dock margins, scroll-up and scroll-down arrow
    537   // presentation, etc.
    538   // The 4 views whose vertical height and origins may be adjusted
    539   // by this function are:
    540   //  1) window, 2) visible content view, 3) scroller view, 4) folder view.
    541 
    542   LayoutMetrics layoutMetrics(windowLeft, windowSize, scrollDelta);
    543   [self gatherMetrics:&layoutMetrics];
    544   [self adjustMetrics:&layoutMetrics];
    545   [self applyMetrics:&layoutMetrics];
    546 }
    547 
    548 - (void)gatherMetrics:(LayoutMetrics*)layoutMetrics {
    549   LayoutMetrics& metrics(*layoutMetrics);
    550   NSWindow* window = [self window];
    551   metrics.windowFrame = [window frame];
    552   metrics.visibleFrame = [visibleView_ frame];
    553   metrics.scrollerFrame = [scrollView_ frame];
    554   metrics.scrollPoint = [scrollView_ documentVisibleRect].origin;
    555   metrics.scrollPoint.y -= metrics.scrollDelta;
    556   metrics.couldScrollUp = ![scrollUpArrowView_ isHidden];
    557   metrics.couldScrollDown = ![scrollDownArrowView_ isHidden];
    558 
    559   metrics.deltaWindowHeight = 0.0;
    560   metrics.deltaWindowY = 0.0;
    561   metrics.deltaVisibleHeight = 0.0;
    562   metrics.deltaVisibleY = 0.0;
    563   metrics.deltaScrollerHeight = 0.0;
    564   metrics.deltaScrollerY = 0.0;
    565 
    566   metrics.minimumY = NSMinY([[window screen] visibleFrame]) +
    567                      bookmarks::kScrollWindowVerticalMargin;
    568   metrics.oldWindowY = NSMinY(metrics.windowFrame);
    569   metrics.folderY =
    570       metrics.scrollerFrame.origin.y + metrics.visibleFrame.origin.y +
    571       metrics.oldWindowY - metrics.scrollPoint.y;
    572   metrics.folderTop = metrics.folderY + NSHeight([folderView_ frame]);
    573 }
    574 
    575 - (void)adjustMetrics:(LayoutMetrics*)layoutMetrics {
    576   LayoutMetrics& metrics(*layoutMetrics);
    577   NSScreen* screen = [[self window] screen];
    578   CGFloat effectiveFolderY = metrics.folderY;
    579   if (!metrics.couldScrollUp && !metrics.couldScrollDown)
    580     effectiveFolderY -= metrics.windowSize.height;
    581   metrics.canScrollUp = effectiveFolderY < metrics.minimumY;
    582   CGFloat maximumY =
    583       NSMaxY([screen visibleFrame]) - bookmarks::kScrollWindowVerticalMargin;
    584   metrics.canScrollDown = metrics.folderTop > maximumY;
    585 
    586   // Accommodate changes in the bottom of the menu.
    587   [self adjustMetricsForMenuBottomChanges:layoutMetrics];
    588 
    589   // Accommodate changes in the top of the menu.
    590   [self adjustMetricsForMenuTopChanges:layoutMetrics];
    591 
    592   metrics.scrollerFrame.origin.y += metrics.deltaScrollerY;
    593   metrics.scrollerFrame.size.height += metrics.deltaScrollerHeight;
    594   metrics.visibleFrame.origin.y += metrics.deltaVisibleY;
    595   metrics.visibleFrame.size.height += metrics.deltaVisibleHeight;
    596   metrics.preScroll = metrics.canScrollUp && !metrics.couldScrollUp &&
    597       metrics.scrollDelta == 0.0 && metrics.deltaWindowHeight >= 0.0;
    598   metrics.windowFrame.origin.y += metrics.deltaWindowY;
    599   metrics.windowFrame.origin.x = metrics.windowLeft;
    600   metrics.windowFrame.size.height += metrics.deltaWindowHeight;
    601   metrics.windowFrame.size.width = metrics.windowSize.width;
    602 }
    603 
    604 - (void)adjustMetricsForMenuBottomChanges:(LayoutMetrics*)layoutMetrics {
    605   LayoutMetrics& metrics(*layoutMetrics);
    606   if (metrics.canScrollUp) {
    607     if (!metrics.couldScrollUp) {
    608       // Couldn't -> Can
    609       metrics.deltaWindowY = -metrics.oldWindowY;
    610       metrics.deltaWindowHeight = -metrics.deltaWindowY;
    611       metrics.deltaVisibleY = metrics.minimumY;
    612       metrics.deltaVisibleHeight = -metrics.deltaVisibleY;
    613       metrics.deltaScrollerY = verticalScrollArrowHeight_;
    614       metrics.deltaScrollerHeight = -metrics.deltaScrollerY;
    615       // Adjust the scroll delta if we've grown the window and it is
    616       // now scroll-up-able, but don't adjust it if we've
    617       // scrolled down and it wasn't scroll-up-able but now is.
    618       if (metrics.canScrollDown == metrics.couldScrollDown) {
    619         CGFloat deltaScroll = metrics.deltaWindowY + metrics.deltaScrollerY +
    620                               metrics.deltaVisibleY;
    621         metrics.scrollPoint.y += deltaScroll + metrics.windowSize.height;
    622       }
    623     } else if (!metrics.canScrollDown && metrics.windowSize.height > 0.0) {
    624       metrics.scrollPoint.y += metrics.windowSize.height;
    625     }
    626   } else {
    627     if (metrics.couldScrollUp) {
    628       // Could -> Can't
    629       metrics.deltaWindowY = metrics.folderY - metrics.oldWindowY;
    630       metrics.deltaWindowHeight = -metrics.deltaWindowY;
    631       metrics.deltaVisibleY = -metrics.visibleFrame.origin.y;
    632       metrics.deltaVisibleHeight = -metrics.deltaVisibleY;
    633       metrics.deltaScrollerY = -verticalScrollArrowHeight_;
    634       metrics.deltaScrollerHeight = -metrics.deltaScrollerY;
    635       // We are no longer scroll-up-able so the scroll point drops to zero.
    636       metrics.scrollPoint.y = 0.0;
    637     } else {
    638       // Couldn't -> Can't
    639       // Check for menu height change by looking at the relative tops of the
    640       // menu folder and the window folder, which previously would have been
    641       // the same.
    642       metrics.deltaWindowY = NSMaxY(metrics.windowFrame) - metrics.folderTop;
    643       metrics.deltaWindowHeight = -metrics.deltaWindowY;
    644     }
    645   }
    646 }
    647 
    648 - (void)adjustMetricsForMenuTopChanges:(LayoutMetrics*)layoutMetrics {
    649   LayoutMetrics& metrics(*layoutMetrics);
    650   if (metrics.canScrollDown == metrics.couldScrollDown) {
    651     if (!metrics.canScrollDown) {
    652       // Not scroll-down-able but the menu top has changed.
    653       metrics.deltaWindowHeight += metrics.scrollDelta;
    654     }
    655   } else {
    656     if (metrics.canScrollDown) {
    657       // Couldn't -> Can
    658       metrics.deltaWindowHeight += (NSMaxY([[[self window] screen]
    659                                     visibleFrame]) -
    660                                     NSMaxY(metrics.windowFrame));
    661       metrics.deltaVisibleHeight -= bookmarks::kScrollWindowVerticalMargin;
    662       metrics.deltaScrollerHeight -= verticalScrollArrowHeight_;
    663     } else {
    664       // Could -> Can't
    665       metrics.deltaWindowHeight -= bookmarks::kScrollWindowVerticalMargin;
    666       metrics.deltaVisibleHeight += bookmarks::kScrollWindowVerticalMargin;
    667       metrics.deltaScrollerHeight += verticalScrollArrowHeight_;
    668     }
    669   }
    670 }
    671 
    672 - (void)applyMetrics:(LayoutMetrics*)layoutMetrics {
    673   LayoutMetrics& metrics(*layoutMetrics);
    674   // Hide or show the scroll arrows.
    675   if (metrics.canScrollUp != metrics.couldScrollUp)
    676     [scrollUpArrowView_ setHidden:metrics.couldScrollUp];
    677   if (metrics.canScrollDown != metrics.couldScrollDown)
    678     [scrollDownArrowView_ setHidden:metrics.couldScrollDown];
    679 
    680   // Adjust the geometry. The order is important because of sizer dependencies.
    681   [scrollView_ setFrame:metrics.scrollerFrame];
    682   [visibleView_ setFrame:metrics.visibleFrame];
    683   // This little bit of trickery handles the one special case where
    684   // the window is now scroll-up-able _and_ going to be resized -- scroll
    685   // first in order to prevent flashing.
    686   if (metrics.preScroll)
    687     [[scrollView_ documentView] scrollPoint:metrics.scrollPoint];
    688 
    689   [[self window] setFrame:metrics.windowFrame display:YES];
    690 
    691   // In all other cases we defer scrolling until the window has been resized
    692   // in order to prevent flashing.
    693   if (!metrics.preScroll)
    694     [[scrollView_ documentView] scrollPoint:metrics.scrollPoint];
    695 
    696   // TODO(maf) find a non-SPI way to do this.
    697   // Hack. This is the only way I've found to get the tracking area cache
    698   // to update properly during a mouse tracking loop.
    699   // Without this, the item tracking-areas are wrong when using a scrollable
    700   // menu with the mouse held down.
    701   NSView *contentView = [[self window] contentView] ;
    702   if ([contentView respondsToSelector:@selector(_updateTrackingAreas)])
    703     [contentView _updateTrackingAreas];
    704 
    705 
    706   if (metrics.canScrollUp != metrics.couldScrollUp ||
    707       metrics.canScrollDown != metrics.couldScrollDown ||
    708       metrics.scrollDelta != 0.0) {
    709     if (metrics.canScrollUp || metrics.canScrollDown)
    710       [self addOrUpdateScrollTracking];
    711     else
    712       [self removeScrollTracking];
    713   }
    714 }
    715 
    716 - (void)adjustWindowForButtonCount:(NSUInteger)buttonCount {
    717   NSRect folderFrame = [folderView_ frame];
    718   CGFloat newMenuHeight =
    719       (CGFloat)[self menuHeightForButtonCount:[buttons_ count]];
    720   CGFloat deltaMenuHeight = newMenuHeight - NSHeight(folderFrame);
    721   // If the height has changed then also change the origin, and adjust the
    722   // scroll (if scrolling).
    723   if ([self canScrollUp]) {
    724     NSPoint scrollPoint = [scrollView_ documentVisibleRect].origin;
    725     scrollPoint.y += deltaMenuHeight;
    726     [[scrollView_ documentView] scrollPoint:scrollPoint];
    727   }
    728   folderFrame.size.height += deltaMenuHeight;
    729   [folderView_ setFrameSize:folderFrame.size];
    730   CGFloat windowWidth = [self adjustButtonWidths] + padding_;
    731   NSPoint newWindowTopLeft = [self windowTopLeftForWidth:windowWidth
    732                                                   height:deltaMenuHeight];
    733   CGFloat left = newWindowTopLeft.x;
    734   NSSize newSize = NSMakeSize(windowWidth, deltaMenuHeight);
    735   [self adjustWindowLeft:left size:newSize scrollingBy:0.0];
    736 }
    737 
    738 // Determine window size and position.
    739 // Create buttons for all our nodes.
    740 // TODO(jrg): break up into more and smaller routines for easier unit testing.
    741 - (void)configureWindow {
    742   const BookmarkNode* node = [parentButton_ bookmarkNode];
    743   DCHECK(node);
    744   int startingIndex = [[parentButton_ cell] startingChildIndex];
    745   DCHECK_LE(startingIndex, node->child_count());
    746   // Must have at least 1 button (for "empty")
    747   int buttons = std::max(node->child_count() - startingIndex, 1);
    748 
    749   // Prelim height of the window.  We'll trim later as needed.
    750   int height = [self menuHeightForButtonCount:buttons];
    751   // We'll need this soon...
    752   [self window];
    753 
    754   // TODO(jrg): combine with frame code in bookmark_bar_controller.mm
    755   // http://crbug.com/35966
    756   NSRect buttonsOuterFrame = NSMakeRect(
    757       0,
    758       height - bookmarks::kBookmarkFolderButtonHeight -
    759           bookmarks::kBookmarkVerticalPadding,
    760       bookmarks::kDefaultBookmarkWidth,
    761       bookmarks::kBookmarkFolderButtonHeight);
    762 
    763   // TODO(jrg): combine with addNodesToButtonList: code from
    764   // bookmark_bar_controller.mm (but use y offset)
    765   // http://crbug.com/35966
    766   if (!node->child_count()) {
    767     // If no children we are the empty button.
    768     BookmarkButton* button = [self makeButtonForNode:nil
    769                                                frame:buttonsOuterFrame];
    770     [buttons_ addObject:button];
    771     [folderView_ addSubview:button];
    772   } else {
    773     for (int i = startingIndex;
    774          i < node->child_count();
    775          i++) {
    776       const BookmarkNode* child = node->GetChild(i);
    777       BookmarkButton* button = [self makeButtonForNode:child
    778                                                  frame:buttonsOuterFrame];
    779       [buttons_ addObject:button];
    780       [folderView_ addSubview:button];
    781       buttonsOuterFrame.origin.y -= bookmarks::kBookmarkFolderButtonHeight;
    782     }
    783   }
    784   [self layOutWindowWithHeight:height];
    785 }
    786 
    787 - (void)layOutWindowWithHeight:(CGFloat)height {
    788   // Lay out the window by adjusting all button widths to be consistent, then
    789   // base the window width on this ideal button width.
    790   CGFloat buttonWidth = [self adjustButtonWidths];
    791   CGFloat windowWidth = buttonWidth + padding_;
    792   NSPoint newWindowTopLeft = [self windowTopLeftForWidth:windowWidth
    793                                                   height:height];
    794   // Make sure as much of a submenu is exposed (which otherwise would be a
    795   // problem if the parent button is close to the bottom of the screen).
    796   if ([parentController_ isKindOfClass:[self class]]) {
    797     CGFloat minimumY = NSMinY([[[self window] screen] visibleFrame]) +
    798                        bookmarks::kScrollWindowVerticalMargin +
    799                        height;
    800     newWindowTopLeft.y = MAX(newWindowTopLeft.y, minimumY);
    801   }
    802   NSWindow* window = [self window];
    803   NSRect windowFrame = NSMakeRect(newWindowTopLeft.x,
    804                                   newWindowTopLeft.y - height,
    805                                   windowWidth, height);
    806   [window setFrame:windowFrame display:NO];
    807   NSRect folderFrame = NSMakeRect(0, 0, windowWidth, height);
    808   [folderView_ setFrame:folderFrame];
    809   NSSize newSize = NSMakeSize(windowWidth, 0.0);
    810   [self adjustWindowLeft:newWindowTopLeft.x size:newSize scrollingBy:0.0];
    811   [self configureWindowLevel];
    812   [window display];
    813 }
    814 
    815 // TODO(mrossetti): See if the following can be moved into view's viewWillDraw:.
    816 - (CGFloat)adjustButtonWidths {
    817   CGFloat width = bookmarks::kBookmarkMenuButtonMinimumWidth;
    818   // Use the cell's size as the base for determining the desired width of the
    819   // button rather than the button's current width. -[cell cellSize] always
    820   // returns the 'optimum' size of the cell based on the cell's contents even
    821   // if it's less than the current button size. Relying on the button size
    822   // would result in buttons that could only get wider but we want to handle
    823   // the case where the widest button gets removed from a folder menu.
    824   for (BookmarkButton* button in buttons_.get())
    825     width = std::max(width, [[button cell] cellSize].width);
    826   width = std::min(width, bookmarks::kBookmarkMenuButtonMaximumWidth);
    827   // Things look and feel more menu-like if all the buttons are the
    828   // full width of the window, especially if there are submenus.
    829   for (BookmarkButton* button in buttons_.get()) {
    830     NSRect buttonFrame = [button frame];
    831     buttonFrame.size.width = width;
    832     [button setFrame:buttonFrame];
    833   }
    834   return width;
    835 }
    836 
    837 // Start a "scroll up" timer.
    838 - (void)beginScrollWindowUp {
    839   [self addScrollTimerWithDelta:kBookmarkBarFolderScrollAmount];
    840 }
    841 
    842 // Start a "scroll down" timer.
    843 - (void)beginScrollWindowDown {
    844   [self addScrollTimerWithDelta:-kBookmarkBarFolderScrollAmount];
    845 }
    846 
    847 // End a scrolling timer.  Can be called excessively with no harm.
    848 - (void)endScroll {
    849   if (scrollTimer_) {
    850     [scrollTimer_ invalidate];
    851     scrollTimer_ = nil;
    852     verticalScrollDelta_ = 0;
    853   }
    854 }
    855 
    856 - (int)indexOfButton:(BookmarkButton*)button {
    857   if (button == nil)
    858     return -1;
    859   int index = [buttons_ indexOfObject:button];
    860   return (index == NSNotFound) ? -1 : index;
    861 }
    862 
    863 - (BookmarkButton*)buttonAtIndex:(int)which {
    864   if (which < 0 || which >= [self buttonCount])
    865     return nil;
    866   return [buttons_ objectAtIndex:which];
    867 }
    868 
    869 // Private, called by performOneScroll only.
    870 // If the button at index contains the mouse it will select it and return YES.
    871 // Otherwise returns NO.
    872 - (BOOL)selectButtonIfHoveredAtIndex:(int)index {
    873   BookmarkButton *btn = [self buttonAtIndex:index];
    874   if ([[btn cell] isMouseReallyInside]) {
    875     buttonThatMouseIsIn_ = btn;
    876     [self setSelectedButtonByIndex:index];
    877     return YES;
    878   }
    879   return NO;
    880 }
    881 
    882 // Perform a single scroll of the specified amount.
    883 - (void)performOneScroll:(CGFloat)delta {
    884   if (delta == 0.0)
    885     return;
    886   CGFloat finalDelta = [self determineFinalScrollDelta:delta];
    887   if (finalDelta == 0.0)
    888     return;
    889   int index = [self indexOfButton:buttonThatMouseIsIn_];
    890   // Check for a current mouse-initiated selection.
    891   BOOL maintainHoverSelection =
    892       (buttonThatMouseIsIn_ &&
    893       [[buttonThatMouseIsIn_ cell] isMouseReallyInside] &&
    894       selectedIndex_ != -1 &&
    895       index == selectedIndex_);
    896   NSRect windowFrame = [[self window] frame];
    897   NSSize newSize = NSMakeSize(NSWidth(windowFrame), 0.0);
    898   [self adjustWindowLeft:windowFrame.origin.x
    899                     size:newSize
    900              scrollingBy:finalDelta];
    901   // We have now scrolled.
    902   if (!maintainHoverSelection)
    903     return;
    904   // Is mouse still in the same hovered button?
    905   if ([[buttonThatMouseIsIn_ cell] isMouseReallyInside])
    906     return;
    907   // The finalDelta scroll direction will tell us us whether to search up or
    908   // down the buttons array for the newly hovered button.
    909   if (finalDelta < 0.0) { // Scrolled up, so search backwards for new hover.
    910     index--;
    911     while (index >= 0) {
    912       if ([self selectButtonIfHoveredAtIndex:index])
    913         return;
    914       index--;
    915     }
    916   } else { // Scrolled down, so search forward for new hovered button.
    917     index++;
    918     int btnMax = [self buttonCount];
    919     while (index < btnMax) {
    920       if ([self selectButtonIfHoveredAtIndex:index])
    921         return;
    922       index++;
    923     }
    924   }
    925 }
    926 
    927 - (CGFloat)determineFinalScrollDelta:(CGFloat)delta {
    928   if ((delta > 0.0 && ![scrollUpArrowView_ isHidden]) ||
    929       (delta < 0.0 && ![scrollDownArrowView_ isHidden])) {
    930     NSWindow* window = [self window];
    931     NSRect windowFrame = [window frame];
    932     NSScreen* screen = [window screen];
    933     NSPoint scrollPosition = [scrollView_ documentVisibleRect].origin;
    934     CGFloat scrollY = scrollPosition.y;
    935     NSRect scrollerFrame = [scrollView_ frame];
    936     CGFloat scrollerY = NSMinY(scrollerFrame);
    937     NSRect visibleFrame = [visibleView_ frame];
    938     CGFloat visibleY = NSMinY(visibleFrame);
    939     CGFloat windowY = NSMinY(windowFrame);
    940     CGFloat offset = scrollerY + visibleY + windowY;
    941 
    942     if (delta > 0.0) {
    943       // Scrolling up.
    944       CGFloat minimumY = NSMinY([screen visibleFrame]) +
    945                          bookmarks::kScrollWindowVerticalMargin;
    946       CGFloat maxUpDelta = scrollY - offset + minimumY;
    947       delta = MIN(delta, maxUpDelta);
    948     } else {
    949       // Scrolling down.
    950       NSRect screenFrame =  [screen visibleFrame];
    951       CGFloat topOfScreen = NSMaxY(screenFrame);
    952       NSRect folderFrame = [folderView_ frame];
    953       CGFloat folderHeight = NSHeight(folderFrame);
    954       CGFloat folderTop = folderHeight - scrollY + offset;
    955       CGFloat maxDownDelta =
    956           topOfScreen - folderTop - bookmarks::kScrollWindowVerticalMargin;
    957       delta = MAX(delta, maxDownDelta);
    958     }
    959   } else {
    960     delta = 0.0;
    961   }
    962   return delta;
    963 }
    964 
    965 // Perform a scroll of the window on the screen.
    966 // Called by a timer when scrolling.
    967 - (void)performScroll:(NSTimer*)timer {
    968   DCHECK(verticalScrollDelta_);
    969   [self performOneScroll:verticalScrollDelta_];
    970 }
    971 
    972 
    973 // Add a timer to fire at a regular interval which scrolls the
    974 // window vertically |delta|.
    975 - (void)addScrollTimerWithDelta:(CGFloat)delta {
    976   if (scrollTimer_ && verticalScrollDelta_ == delta)
    977     return;
    978   [self endScroll];
    979   verticalScrollDelta_ = delta;
    980   scrollTimer_ = [NSTimer timerWithTimeInterval:kBookmarkBarFolderScrollInterval
    981                                          target:self
    982                                        selector:@selector(performScroll:)
    983                                        userInfo:nil
    984                                         repeats:YES];
    985 
    986   [[NSRunLoop mainRunLoop] addTimer:scrollTimer_ forMode:NSRunLoopCommonModes];
    987 }
    988 
    989 
    990 // Called as a result of our tracking area.  Warning: on the main
    991 // screen (of a single-screened machine), the minimum mouse y value is
    992 // 1, not 0.  Also, we do not get events when the mouse is above the
    993 // menubar (to be fixed by setting the proper window level; see
    994 // initializer).
    995 // Note [theEvent window] may not be our window, as we also get these messages
    996 // forwarded from BookmarkButton's mouse tracking loop.
    997 - (void)mouseMovedOrDragged:(NSEvent*)theEvent {
    998   NSPoint eventScreenLocation =
    999       [[theEvent window] convertBaseToScreen:[theEvent locationInWindow]];
   1000 
   1001   // Base hot spot calculations on the positions of the scroll arrow views.
   1002   NSRect testRect = [scrollDownArrowView_ frame];
   1003   NSPoint testPoint = [visibleView_ convertPoint:testRect.origin
   1004                                                   toView:nil];
   1005   testPoint = [[self window] convertBaseToScreen:testPoint];
   1006   CGFloat closeToTopOfScreen = testPoint.y;
   1007 
   1008   testRect = [scrollUpArrowView_ frame];
   1009   testPoint = [visibleView_ convertPoint:testRect.origin toView:nil];
   1010   testPoint = [[self window] convertBaseToScreen:testPoint];
   1011   CGFloat closeToBottomOfScreen = testPoint.y + testRect.size.height;
   1012   if (eventScreenLocation.y <= closeToBottomOfScreen &&
   1013       ![scrollUpArrowView_ isHidden]) {
   1014     [self beginScrollWindowUp];
   1015   } else if (eventScreenLocation.y > closeToTopOfScreen &&
   1016       ![scrollDownArrowView_ isHidden]) {
   1017     [self beginScrollWindowDown];
   1018   } else {
   1019     [self endScroll];
   1020   }
   1021 }
   1022 
   1023 - (void)mouseMoved:(NSEvent*)theEvent {
   1024   [self mouseMovedOrDragged:theEvent];
   1025 }
   1026 
   1027 - (void)mouseDragged:(NSEvent*)theEvent {
   1028   [self mouseMovedOrDragged:theEvent];
   1029 }
   1030 
   1031 - (void)mouseExited:(NSEvent*)theEvent {
   1032   [self endScroll];
   1033 }
   1034 
   1035 // Add a tracking area so we know when the mouse is pinned to the top
   1036 // or bottom of the screen.  If that happens, and if the mouse
   1037 // position overlaps the window, scroll it.
   1038 - (void)addOrUpdateScrollTracking {
   1039   [self removeScrollTracking];
   1040   NSView* view = [[self window] contentView];
   1041   scrollTrackingArea_.reset([[CrTrackingArea alloc]
   1042                               initWithRect:[view bounds]
   1043                                    options:(NSTrackingMouseMoved |
   1044                                             NSTrackingMouseEnteredAndExited |
   1045                                             NSTrackingActiveAlways |
   1046                                             NSTrackingEnabledDuringMouseDrag
   1047                                             )
   1048                               proxiedOwner:self
   1049                                   userInfo:nil]);
   1050   [view addTrackingArea:scrollTrackingArea_.get()];
   1051 }
   1052 
   1053 // Remove the tracking area associated with scrolling.
   1054 - (void)removeScrollTracking {
   1055   if (scrollTrackingArea_.get()) {
   1056     [[[self window] contentView] removeTrackingArea:scrollTrackingArea_.get()];
   1057     [scrollTrackingArea_.get() clearOwner];
   1058   }
   1059   scrollTrackingArea_.reset();
   1060 }
   1061 
   1062 // Close the old hover-open bookmark folder, and open a new one.  We
   1063 // do both in one step to allow for a delay in closing the old one.
   1064 // See comments above kDragHoverCloseDelay (bookmark_bar_controller.h)
   1065 // for more details.
   1066 - (void)openBookmarkFolderFromButtonAndCloseOldOne:(id)sender {
   1067   // If an old submenu exists, close it immediately.
   1068   [self closeBookmarkFolder:sender];
   1069 
   1070   // Open a new one if meaningful.
   1071   if ([sender isFolder])
   1072     [folderTarget_ openBookmarkFolderFromButton:sender];
   1073 }
   1074 
   1075 - (NSArray*)buttons {
   1076   return buttons_.get();
   1077 }
   1078 
   1079 - (void)close {
   1080   [folderController_ close];
   1081   [super close];
   1082 }
   1083 
   1084 - (void)scrollWheel:(NSEvent *)theEvent {
   1085   if (![scrollUpArrowView_ isHidden] || ![scrollDownArrowView_ isHidden]) {
   1086     // We go negative since an NSScrollView has a flipped coordinate frame.
   1087     CGFloat amt = kBookmarkBarFolderScrollWheelAmount * -[theEvent deltaY];
   1088     [self performOneScroll:amt];
   1089   }
   1090 }
   1091 
   1092 #pragma mark Actions Forwarded to Parent BookmarkBarController
   1093 
   1094 - (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)item {
   1095   return [barController_ validateUserInterfaceItem:item];
   1096 }
   1097 
   1098 - (IBAction)openBookmark:(id)sender {
   1099   [barController_ openBookmark:sender];
   1100 }
   1101 
   1102 - (IBAction)openBookmarkInNewForegroundTab:(id)sender {
   1103   [barController_ openBookmarkInNewForegroundTab:sender];
   1104 }
   1105 
   1106 - (IBAction)openBookmarkInNewWindow:(id)sender {
   1107   [barController_ openBookmarkInNewWindow:sender];
   1108 }
   1109 
   1110 - (IBAction)openBookmarkInIncognitoWindow:(id)sender {
   1111   [barController_ openBookmarkInIncognitoWindow:sender];
   1112 }
   1113 
   1114 - (IBAction)editBookmark:(id)sender {
   1115   [barController_ editBookmark:sender];
   1116 }
   1117 
   1118 - (IBAction)cutBookmark:(id)sender {
   1119   [self closeBookmarkFolder:self];
   1120   [barController_ cutBookmark:sender];
   1121 }
   1122 
   1123 - (IBAction)copyBookmark:(id)sender {
   1124   [barController_ copyBookmark:sender];
   1125 }
   1126 
   1127 - (IBAction)pasteBookmark:(id)sender {
   1128   [barController_ pasteBookmark:sender];
   1129 }
   1130 
   1131 - (IBAction)deleteBookmark:(id)sender {
   1132   [self closeBookmarkFolder:self];
   1133   [barController_ deleteBookmark:sender];
   1134 }
   1135 
   1136 - (IBAction)openAllBookmarks:(id)sender {
   1137   [barController_ openAllBookmarks:sender];
   1138 }
   1139 
   1140 - (IBAction)openAllBookmarksNewWindow:(id)sender {
   1141   [barController_ openAllBookmarksNewWindow:sender];
   1142 }
   1143 
   1144 - (IBAction)openAllBookmarksIncognitoWindow:(id)sender {
   1145   [barController_ openAllBookmarksIncognitoWindow:sender];
   1146 }
   1147 
   1148 - (IBAction)addPage:(id)sender {
   1149   [barController_ addPage:sender];
   1150 }
   1151 
   1152 - (IBAction)addFolder:(id)sender {
   1153   [barController_ addFolder:sender];
   1154 }
   1155 
   1156 #pragma mark Drag & Drop
   1157 
   1158 // Find something like std::is_between<T>?  I can't believe one doesn't exist.
   1159 // http://crbug.com/35966
   1160 static BOOL ValueInRangeInclusive(CGFloat low, CGFloat value, CGFloat high) {
   1161   return ((value >= low) && (value <= high));
   1162 }
   1163 
   1164 // Return the proposed drop target for a hover open button, or nil if none.
   1165 //
   1166 // TODO(jrg): this is just like the version in
   1167 // bookmark_bar_controller.mm, but vertical instead of horizontal.
   1168 // Generalize to be axis independent then share code.
   1169 // http://crbug.com/35966
   1170 - (BookmarkButton*)buttonForDroppingOnAtPoint:(NSPoint)point {
   1171   for (BookmarkButton* button in buttons_.get()) {
   1172     // No early break -- makes no assumption about button ordering.
   1173 
   1174     // Intentionally NOT using NSPointInRect() so that scrolling into
   1175     // a submenu doesn't cause it to be closed.
   1176     if (ValueInRangeInclusive(NSMinY([button frame]),
   1177                               point.y,
   1178                               NSMaxY([button frame]))) {
   1179 
   1180       // Over a button but let's be a little more specific
   1181       // (e.g. over the middle half).
   1182       NSRect frame = [button frame];
   1183       NSRect middleHalfOfButton = NSInsetRect(frame, 0, frame.size.height / 4);
   1184       if (ValueInRangeInclusive(NSMinY(middleHalfOfButton),
   1185                                 point.y,
   1186                                 NSMaxY(middleHalfOfButton))) {
   1187         // It makes no sense to drop on a non-folder; there is no hover.
   1188         if (![button isFolder])
   1189           return nil;
   1190         // Got it!
   1191         return button;
   1192       } else {
   1193         // Over a button but not over the middle half.
   1194         return nil;
   1195       }
   1196     }
   1197   }
   1198   // Not hovering over a button.
   1199   return nil;
   1200 }
   1201 
   1202 // TODO(jrg): again we have code dup, sort of, with
   1203 // bookmark_bar_controller.mm, but the axis is changed.  One minor
   1204 // difference is accomodation for the "empty" button (which may not
   1205 // exist in the future).
   1206 // http://crbug.com/35966
   1207 - (int)indexForDragToPoint:(NSPoint)point {
   1208   // Identify which buttons we are between.  For now, assume a button
   1209   // location is at the center point of its view, and that an exact
   1210   // match means "place before".
   1211   // TODO(jrg): revisit position info based on UI team feedback.
   1212   // dropLocation is in bar local coordinates.
   1213   // http://crbug.com/36276
   1214   NSPoint dropLocation =
   1215       [folderView_ convertPoint:point
   1216                      fromView:[[self window] contentView]];
   1217   BookmarkButton* buttonToTheTopOfDraggedButton = nil;
   1218   // Buttons are laid out in this array from top to bottom (screen
   1219   // wise), which means "biggest y" --> "smallest y".
   1220   for (BookmarkButton* button in buttons_.get()) {
   1221     CGFloat midpoint = NSMidY([button frame]);
   1222     if (dropLocation.y > midpoint) {
   1223       break;
   1224     }
   1225     buttonToTheTopOfDraggedButton = button;
   1226   }
   1227 
   1228   // TODO(jrg): On Windows, dropping onto (empty) highlights the
   1229   // entire drop location and does not use an insertion point.
   1230   // http://crbug.com/35967
   1231   if (!buttonToTheTopOfDraggedButton) {
   1232     // We are at the very top (we broke out of the loop on the first try).
   1233     return 0;
   1234   }
   1235   if ([buttonToTheTopOfDraggedButton isEmpty]) {
   1236     // There is a button but it's an empty placeholder.
   1237     // Default to inserting on top of it.
   1238     return 0;
   1239   }
   1240   const BookmarkNode* beforeNode = [buttonToTheTopOfDraggedButton
   1241                                        bookmarkNode];
   1242   DCHECK(beforeNode);
   1243   // Be careful if the number of buttons != number of nodes.
   1244   return ((beforeNode->parent()->GetIndexOf(beforeNode) + 1) -
   1245           [[parentButton_ cell] startingChildIndex]);
   1246 }
   1247 
   1248 // TODO(jrg): Yet more code dup.
   1249 // http://crbug.com/35966
   1250 - (BOOL)dragBookmark:(const BookmarkNode*)sourceNode
   1251                   to:(NSPoint)point
   1252                 copy:(BOOL)copy {
   1253   DCHECK(sourceNode);
   1254 
   1255   // Drop destination.
   1256   const BookmarkNode* destParent = NULL;
   1257   int destIndex = 0;
   1258 
   1259   // First check if we're dropping on a button.  If we have one, and
   1260   // it's a folder, drop in it.
   1261   BookmarkButton* button = [self buttonForDroppingOnAtPoint:point];
   1262   if ([button isFolder]) {
   1263     destParent = [button bookmarkNode];
   1264     // Drop it at the end.
   1265     destIndex = [button bookmarkNode]->child_count();
   1266   } else {
   1267     // Else we're dropping somewhere in the folder, so find the right spot.
   1268     destParent = [parentButton_ bookmarkNode];
   1269     destIndex = [self indexForDragToPoint:point];
   1270     // Be careful if the number of buttons != number of nodes.
   1271     destIndex += [[parentButton_ cell] startingChildIndex];
   1272   }
   1273 
   1274   // Prevent cycles.
   1275   BOOL wasCopiedOrMoved = NO;
   1276   if (!destParent->HasAncestor(sourceNode)) {
   1277     if (copy)
   1278       [self bookmarkModel]->Copy(sourceNode, destParent, destIndex);
   1279     else
   1280       [self bookmarkModel]->Move(sourceNode, destParent, destIndex);
   1281     wasCopiedOrMoved = YES;
   1282     // Movement of a node triggers observers (like us) to rebuild the
   1283     // bar so we don't have to do so explicitly.
   1284   }
   1285 
   1286   return wasCopiedOrMoved;
   1287 }
   1288 
   1289 // TODO(maf): Implement live drag & drop animation using this hook.
   1290 - (void)setDropInsertionPos:(CGFloat)where {
   1291 }
   1292 
   1293 // TODO(maf): Implement live drag & drop animation using this hook.
   1294 - (void)clearDropInsertionPos {
   1295 }
   1296 
   1297 #pragma mark NSWindowDelegate Functions
   1298 
   1299 - (void)windowWillClose:(NSNotification*)notification {
   1300   // Also done by the dealloc method, but also doing it here is quicker and
   1301   // more reliable.
   1302   [parentButton_ forceButtonBorderToStayOnAlways:NO];
   1303 
   1304   // If a "hover open" is pending when the bookmark bar folder is
   1305   // closed, be sure it gets cancelled.
   1306   [NSObject cancelPreviousPerformRequestsWithTarget:self];
   1307 
   1308   [self endScroll];  // Just in case we were scrolling.
   1309   [barController_ childFolderWillClose:self];
   1310   [self closeBookmarkFolder:self];
   1311   [self autorelease];
   1312 }
   1313 
   1314 #pragma mark BookmarkButtonDelegate Protocol
   1315 
   1316 - (void)fillPasteboard:(NSPasteboard*)pboard
   1317        forDragOfButton:(BookmarkButton*)button {
   1318   [[self folderTarget] fillPasteboard:pboard forDragOfButton:button];
   1319 
   1320   // Close our folder menu and submenus since we know we're going to be dragged.
   1321   [self closeBookmarkFolder:self];
   1322 }
   1323 
   1324 // Called from BookmarkButton.
   1325 // Unlike bookmark_bar_controller's version, we DO default to being enabled.
   1326 - (void)mouseEnteredButton:(id)sender event:(NSEvent*)event {
   1327   [[NSCursor arrowCursor] set];
   1328 
   1329   buttonThatMouseIsIn_ = sender;
   1330   [self setSelectedButtonByIndex:[self indexOfButton:sender]];
   1331 
   1332   // Cancel a previous hover if needed.
   1333   [NSObject cancelPreviousPerformRequestsWithTarget:self];
   1334 
   1335   // If already opened, then we exited but re-entered the button
   1336   // (without entering another button open), do nothing.
   1337   if ([folderController_ parentButton] == sender)
   1338     return;
   1339 
   1340   [self performSelector:@selector(openBookmarkFolderFromButtonAndCloseOldOne:)
   1341              withObject:sender
   1342              afterDelay:bookmarks::kHoverOpenDelay
   1343                 inModes:[NSArray arrayWithObject:NSRunLoopCommonModes]];
   1344 }
   1345 
   1346 // Called from the BookmarkButton
   1347 - (void)mouseExitedButton:(id)sender event:(NSEvent*)event {
   1348   if (buttonThatMouseIsIn_ == sender)
   1349     buttonThatMouseIsIn_ = nil;
   1350     [self setSelectedButtonByIndex:-1];
   1351 
   1352   // Stop any timer about opening a new hover-open folder.
   1353 
   1354   // Since a performSelector:withDelay: on self retains self, it is
   1355   // possible that a cancelPreviousPerformRequestsWithTarget: reduces
   1356   // the refcount to 0, releasing us.  That's a bad thing to do while
   1357   // this object (or others it may own) is in the event chain.  Thus
   1358   // we have a retain/autorelease.
   1359   [self retain];
   1360   [NSObject cancelPreviousPerformRequestsWithTarget:self];
   1361   [self autorelease];
   1362 }
   1363 
   1364 - (NSWindow*)browserWindow {
   1365   return [parentController_ browserWindow];
   1366 }
   1367 
   1368 - (BOOL)canDragBookmarkButtonToTrash:(BookmarkButton*)button {
   1369   return [barController_ canEditBookmarks] &&
   1370          [barController_ canEditBookmark:[button bookmarkNode]];
   1371 }
   1372 
   1373 - (void)didDragBookmarkToTrash:(BookmarkButton*)button {
   1374   [barController_ didDragBookmarkToTrash:button];
   1375 }
   1376 
   1377 - (void)bookmarkDragDidEnd:(BookmarkButton*)button
   1378                  operation:(NSDragOperation)operation {
   1379   [barController_ bookmarkDragDidEnd:button
   1380                            operation:operation];
   1381 }
   1382 
   1383 
   1384 #pragma mark BookmarkButtonControllerProtocol
   1385 
   1386 // Recursively close all bookmark folders.
   1387 - (void)closeAllBookmarkFolders {
   1388   // Closing the top level implicitly closes all children.
   1389   [barController_ closeAllBookmarkFolders];
   1390 }
   1391 
   1392 // Close our bookmark folder (a sub-controller) if we have one.
   1393 - (void)closeBookmarkFolder:(id)sender {
   1394   if (folderController_) {
   1395     // Make this menu key, so key status doesn't go back to the browser
   1396     // window when the submenu closes.
   1397     [[self window] makeKeyWindow];
   1398     [self setSubFolderGrowthToRight:YES];
   1399     [[folderController_ window] close];
   1400     folderController_ = nil;
   1401   }
   1402 }
   1403 
   1404 - (BookmarkModel*)bookmarkModel {
   1405   return [barController_ bookmarkModel];
   1406 }
   1407 
   1408 - (BOOL)draggingAllowed:(id<NSDraggingInfo>)info {
   1409   return [barController_ draggingAllowed:info];
   1410 }
   1411 
   1412 // TODO(jrg): Refactor BookmarkBarFolder common code. http://crbug.com/35966
   1413 // Most of the work (e.g. drop indicator) is taken care of in the
   1414 // folder_view.  Here we handle hover open issues for subfolders.
   1415 // Caution: there are subtle differences between this one and
   1416 // bookmark_bar_controller.mm's version.
   1417 - (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)info {
   1418   NSPoint currentLocation = [info draggingLocation];
   1419   BookmarkButton* button = [self buttonForDroppingOnAtPoint:currentLocation];
   1420 
   1421   // Don't allow drops that would result in cycles.
   1422   if (button) {
   1423     NSData* data = [[info draggingPasteboard]
   1424                     dataForType:kBookmarkButtonDragType];
   1425     if (data && [info draggingSource]) {
   1426       BookmarkButton* sourceButton = nil;
   1427       [data getBytes:&sourceButton length:sizeof(sourceButton)];
   1428       const BookmarkNode* sourceNode = [sourceButton bookmarkNode];
   1429       const BookmarkNode* destNode = [button bookmarkNode];
   1430       if (destNode->HasAncestor(sourceNode))
   1431         button = nil;
   1432     }
   1433   }
   1434   // Delegate handling of dragging over a button to the |hoverState_| member.
   1435   return [hoverState_ draggingEnteredButton:button];
   1436 }
   1437 
   1438 - (NSDragOperation)draggingUpdated:(id<NSDraggingInfo>)info {
   1439   return NSDragOperationMove;
   1440 }
   1441 
   1442 // Unlike bookmark_bar_controller, we need to keep track of dragging state.
   1443 // We also need to make sure we cancel the delayed hover close.
   1444 - (void)draggingExited:(id<NSDraggingInfo>)info {
   1445   // NOT the same as a cancel --> we may have moved the mouse into the submenu.
   1446   // Delegate handling of the hover button to the |hoverState_| member.
   1447   [hoverState_ draggingExited];
   1448 }
   1449 
   1450 - (BOOL)dragShouldLockBarVisibility {
   1451   return [parentController_ dragShouldLockBarVisibility];
   1452 }
   1453 
   1454 // TODO(jrg): ARGH more code dup.
   1455 // http://crbug.com/35966
   1456 - (BOOL)dragButton:(BookmarkButton*)sourceButton
   1457                 to:(NSPoint)point
   1458               copy:(BOOL)copy {
   1459   DCHECK([sourceButton isKindOfClass:[BookmarkButton class]]);
   1460   const BookmarkNode* sourceNode = [sourceButton bookmarkNode];
   1461   return [self dragBookmark:sourceNode to:point copy:copy];
   1462 }
   1463 
   1464 // TODO(mrossetti,jrg): Identical to the same function in BookmarkBarController.
   1465 // http://crbug.com/35966
   1466 - (BOOL)dragBookmarkData:(id<NSDraggingInfo>)info {
   1467   BOOL dragged = NO;
   1468   std::vector<const BookmarkNode*> nodes([self retrieveBookmarkNodeData]);
   1469   if (nodes.size()) {
   1470     BOOL copy = !([info draggingSourceOperationMask] & NSDragOperationMove);
   1471     NSPoint dropPoint = [info draggingLocation];
   1472     for (std::vector<const BookmarkNode*>::const_iterator it = nodes.begin();
   1473          it != nodes.end(); ++it) {
   1474       const BookmarkNode* sourceNode = *it;
   1475       dragged = [self dragBookmark:sourceNode to:dropPoint copy:copy];
   1476     }
   1477   }
   1478   return dragged;
   1479 }
   1480 
   1481 // TODO(mrossetti,jrg): Identical to the same function in BookmarkBarController.
   1482 // http://crbug.com/35966
   1483 - (std::vector<const BookmarkNode*>)retrieveBookmarkNodeData {
   1484   std::vector<const BookmarkNode*> dragDataNodes;
   1485   BookmarkNodeData dragData;
   1486   if(dragData.ReadFromDragClipboard()) {
   1487     BookmarkModel* bookmarkModel = [self bookmarkModel];
   1488     Profile* profile = bookmarkModel->profile();
   1489     std::vector<const BookmarkNode*> nodes(dragData.GetNodes(profile));
   1490     dragDataNodes.assign(nodes.begin(), nodes.end());
   1491   }
   1492   return dragDataNodes;
   1493 }
   1494 
   1495 // Return YES if we should show the drop indicator, else NO.
   1496 // TODO(jrg): ARGH code dup!
   1497 // http://crbug.com/35966
   1498 - (BOOL)shouldShowIndicatorShownForPoint:(NSPoint)point {
   1499   return ![self buttonForDroppingOnAtPoint:point];
   1500 }
   1501 
   1502 // Button selection change code to support type to select and arrow key events.
   1503 #pragma mark Keyboard Support
   1504 
   1505 // Scroll the menu to show the selected button, if it's not already visible.
   1506 - (void)showSelectedButton {
   1507   int bMaxIndex = [self buttonCount] - 1; // Max array index in button array.
   1508 
   1509   // Is there a valid selected button?
   1510   if (bMaxIndex < 0 || selectedIndex_ < 0 || selectedIndex_ > bMaxIndex)
   1511     return;
   1512 
   1513   // Is the menu scrollable anyway?
   1514   if (![self canScrollUp] && ![self canScrollDown])
   1515     return;
   1516 
   1517   // Now check to see if we need to scroll, which way, and how far.
   1518   CGFloat delta = 0.0;
   1519   NSPoint scrollPoint = [scrollView_ documentVisibleRect].origin;
   1520   CGFloat itemBottom = (bMaxIndex - selectedIndex_) *
   1521       bookmarks::kBookmarkFolderButtonHeight;
   1522   CGFloat itemTop = itemBottom + bookmarks::kBookmarkFolderButtonHeight;
   1523   CGFloat viewHeight = NSHeight([scrollView_  frame]);
   1524 
   1525   if (scrollPoint.y > itemBottom) { // Need to scroll down.
   1526     delta = scrollPoint.y - itemBottom;
   1527   } else if ((scrollPoint.y + viewHeight) < itemTop) { // Need to scroll up.
   1528     delta = -(itemTop - (scrollPoint.y + viewHeight));
   1529   } else { // No need to scroll.
   1530     return;
   1531   }
   1532 
   1533   [self performOneScroll:delta];
   1534 }
   1535 
   1536 // All changes to selectedness of buttons (aka fake menu items) ends up
   1537 // calling this method to actually flip the state of items.
   1538 // Needs to handle -1 as the invalid index (when nothing is selected) and
   1539 // greater than range values too.
   1540 - (void)setStateOfButtonByIndex:(int)index
   1541                           state:(bool)state {
   1542   if (index >= 0 && index < [self buttonCount])
   1543     [[buttons_ objectAtIndex:index] highlight:state];
   1544 }
   1545 
   1546 // Selects the required button and deselects the previously selected one.
   1547 // An index of -1 means no selection.
   1548 - (void)setSelectedButtonByIndex:(int)index {
   1549   if (index == selectedIndex_)
   1550     return;
   1551 
   1552   [self setStateOfButtonByIndex:selectedIndex_ state:NO];
   1553   [self setStateOfButtonByIndex:index state:YES];
   1554   selectedIndex_ = index;
   1555 
   1556   [self showSelectedButton];
   1557 }
   1558 
   1559 - (void)clearInputText {
   1560   [typedPrefix_ release];
   1561   typedPrefix_ = nil;
   1562 }
   1563 
   1564 // Find the earliest item in the folder which has the target prefix.
   1565 // Returns nil if there is no prefix or there are no matches.
   1566 // These are in no particular order, and not particularly numerous, so linear
   1567 // search should be OK.
   1568 // -1 means no match.
   1569 - (int)earliestBookmarkIndexWithPrefix:(NSString*)prefix {
   1570   if ([prefix length] == 0) // Also handles nil.
   1571     return -1;
   1572   int maxButtons = [buttons_ count];
   1573   NSString *lowercasePrefix = [prefix lowercaseString];
   1574   for (int i = 0 ; i < maxButtons ; ++i) {
   1575     BookmarkButton* button = [buttons_ objectAtIndex:i];
   1576     if ([[[button title] lowercaseString] hasPrefix:lowercasePrefix])
   1577       return i;
   1578   }
   1579   return -1;
   1580 }
   1581 
   1582 - (void)setSelectedButtonByPrefix:(NSString*)prefix {
   1583   [self setSelectedButtonByIndex:[self earliestBookmarkIndexWithPrefix:prefix]];
   1584 }
   1585 
   1586 - (void)selectPrevious {
   1587   int newIndex;
   1588   if (selectedIndex_ == 0)
   1589     return;
   1590   if (selectedIndex_ < 0)
   1591     newIndex = [self buttonCount] -1;
   1592   else
   1593     newIndex = std::max(selectedIndex_ - 1, 0);
   1594   [self setSelectedButtonByIndex:newIndex];
   1595 }
   1596 
   1597 - (void) selectNext {
   1598   if (selectedIndex_ + 1 < [self buttonCount])
   1599     [self setSelectedButtonByIndex:selectedIndex_ + 1];
   1600 }
   1601 
   1602 - (BOOL)handleInputText:(NSString*)newText {
   1603   const unichar kUnicodeEscape = 0x001B;
   1604   const unichar kUnicodeSpace = 0x0020;
   1605 
   1606   // Event goes to the deepest nested open submenu.
   1607   if (folderController_)
   1608     return [folderController_ handleInputText:newText];
   1609 
   1610   // Look for arrow keys or other function keys.
   1611   if ([newText length] == 1) {
   1612     // Get the 16-bit unicode char.
   1613     unichar theChar = [newText characterAtIndex:0];
   1614     switch (theChar) {
   1615 
   1616       // Keys that trigger opening of the selection.
   1617       case kUnicodeSpace: // Space.
   1618       case NSNewlineCharacter:
   1619       case NSCarriageReturnCharacter:
   1620       case NSEnterCharacter:
   1621         if (selectedIndex_ >= 0 && selectedIndex_ < [self buttonCount]) {
   1622           [self openBookmark:[buttons_ objectAtIndex:selectedIndex_]];
   1623           return NO; // NO because the selection-handling code will close later.
   1624         } else {
   1625           return YES; // Triggering with no selection closes the menu.
   1626         }
   1627       // Keys that cancel and close the menu.
   1628       case kUnicodeEscape:
   1629       case NSDeleteCharacter:
   1630       case NSBackspaceCharacter:
   1631         [self clearInputText];
   1632         return YES;
   1633       // Keys that change selection directionally.
   1634       case NSUpArrowFunctionKey:
   1635         [self clearInputText];
   1636         [self selectPrevious];
   1637         return NO;
   1638       case NSDownArrowFunctionKey:
   1639         [self clearInputText];
   1640         [self selectNext];
   1641         return NO;
   1642       // Keys that open and close submenus.
   1643       case NSRightArrowFunctionKey: {
   1644         BookmarkButton* btn = [self buttonAtIndex:selectedIndex_];
   1645         if (btn && [btn isFolder]) {
   1646           [self openBookmarkFolderFromButtonAndCloseOldOne:btn];
   1647           [folderController_ selectNext];
   1648         }
   1649         [self clearInputText];
   1650         return NO;
   1651       }
   1652       case NSLeftArrowFunctionKey:
   1653         [self clearInputText];
   1654         [parentController_ closeBookmarkFolder:self];
   1655         return NO;
   1656 
   1657       // Check for other keys that should close the menu.
   1658       default: {
   1659         if (theChar > NSUpArrowFunctionKey &&
   1660             theChar <= NSModeSwitchFunctionKey) {
   1661           [self clearInputText];
   1662           return YES;
   1663         }
   1664         break;
   1665       }
   1666     }
   1667   }
   1668 
   1669   // It is a char or string worth adding to the type-select buffer.
   1670   NSString *newString = (!typedPrefix_) ?
   1671       newText : [typedPrefix_ stringByAppendingString:newText];
   1672   [typedPrefix_ release];
   1673   typedPrefix_ = [newString retain];
   1674   [self setSelectedButtonByPrefix:typedPrefix_];
   1675   return NO;
   1676 }
   1677 
   1678 // Return the y position for a drop indicator.
   1679 //
   1680 // TODO(jrg): again we have code dup, sort of, with
   1681 // bookmark_bar_controller.mm, but the axis is changed.
   1682 // http://crbug.com/35966
   1683 - (CGFloat)indicatorPosForDragToPoint:(NSPoint)point {
   1684   CGFloat y = 0;
   1685   int destIndex = [self indexForDragToPoint:point];
   1686   int numButtons = static_cast<int>([buttons_ count]);
   1687 
   1688   // If it's a drop strictly between existing buttons or at the very beginning
   1689   if (destIndex >= 0 && destIndex < numButtons) {
   1690     // ... put the indicator right between the buttons.
   1691     BookmarkButton* button =
   1692         [buttons_ objectAtIndex:static_cast<NSUInteger>(destIndex)];
   1693     DCHECK(button);
   1694     NSRect buttonFrame = [button frame];
   1695     y = NSMaxY(buttonFrame) + 0.5 * bookmarks::kBookmarkVerticalPadding;
   1696 
   1697     // If it's a drop at the end (past the last button, if there are any) ...
   1698   } else if (destIndex == numButtons) {
   1699     // and if it's past the last button ...
   1700     if (numButtons > 0) {
   1701       // ... find the last button, and put the indicator below it.
   1702       BookmarkButton* button =
   1703           [buttons_ objectAtIndex:static_cast<NSUInteger>(destIndex - 1)];
   1704       DCHECK(button);
   1705       NSRect buttonFrame = [button frame];
   1706       y = buttonFrame.origin.y - 0.5 * bookmarks::kBookmarkVerticalPadding;
   1707 
   1708     }
   1709   } else {
   1710     NOTREACHED();
   1711   }
   1712 
   1713   return y;
   1714 }
   1715 
   1716 - (ui::ThemeProvider*)themeProvider {
   1717   return [parentController_ themeProvider];
   1718 }
   1719 
   1720 - (void)childFolderWillShow:(id<BookmarkButtonControllerProtocol>)child {
   1721   // Do nothing.
   1722 }
   1723 
   1724 - (void)childFolderWillClose:(id<BookmarkButtonControllerProtocol>)child {
   1725   // Do nothing.
   1726 }
   1727 
   1728 - (BookmarkBarFolderController*)folderController {
   1729   return folderController_;
   1730 }
   1731 
   1732 - (void)faviconLoadedForNode:(const BookmarkNode*)node {
   1733   for (BookmarkButton* button in buttons_.get()) {
   1734     if ([button bookmarkNode] == node) {
   1735       [button setImage:[barController_ faviconForNode:node]];
   1736       [button setNeedsDisplay:YES];
   1737       return;
   1738     }
   1739   }
   1740 
   1741   // Node was not in this menu, try submenu.
   1742   if (folderController_)
   1743     [folderController_ faviconLoadedForNode:node];
   1744 }
   1745 
   1746 // Add a new folder controller as triggered by the given folder button.
   1747 - (void)addNewFolderControllerWithParentButton:(BookmarkButton*)parentButton {
   1748   if (folderController_)
   1749     [self closeBookmarkFolder:self];
   1750 
   1751   // Folder controller, like many window controllers, owns itself.
   1752   folderController_ =
   1753       [[BookmarkBarFolderController alloc] initWithParentButton:parentButton
   1754                                                parentController:self
   1755                                                   barController:barController_];
   1756   [folderController_ showWindow:self];
   1757 }
   1758 
   1759 - (void)openAll:(const BookmarkNode*)node
   1760     disposition:(WindowOpenDisposition)disposition {
   1761   [barController_ openAll:node disposition:disposition];
   1762 }
   1763 
   1764 - (void)addButtonForNode:(const BookmarkNode*)node
   1765                  atIndex:(NSInteger)buttonIndex {
   1766   // Propose the frame for the new button. By default, this will be set to the
   1767   // topmost button's frame (and there will always be one) offset upward in
   1768   // anticipation of insertion.
   1769   NSRect newButtonFrame = [[buttons_ objectAtIndex:0] frame];
   1770   newButtonFrame.origin.y += bookmarks::kBookmarkFolderButtonHeight;
   1771   // When adding a button to an empty folder we must remove the 'empty'
   1772   // placeholder button. This can be detected by checking for a parent
   1773   // child count of 1.
   1774   const BookmarkNode* parentNode = node->parent();
   1775   if (parentNode->child_count() == 1) {
   1776     BookmarkButton* emptyButton = [buttons_ lastObject];
   1777     newButtonFrame = [emptyButton frame];
   1778     [emptyButton setDelegate:nil];
   1779     [emptyButton removeFromSuperview];
   1780     [buttons_ removeLastObject];
   1781   }
   1782 
   1783   if (buttonIndex == -1 || buttonIndex > (NSInteger)[buttons_ count])
   1784     buttonIndex = [buttons_ count];
   1785 
   1786   // Offset upward by one button height all buttons above insertion location.
   1787   BookmarkButton* button = nil;  // Remember so it can be de-highlighted.
   1788   for (NSInteger i = 0; i < buttonIndex; ++i) {
   1789     button = [buttons_ objectAtIndex:i];
   1790     // Remember this location in case it's the last button being moved
   1791     // which is where the new button will be located.
   1792     newButtonFrame = [button frame];
   1793     NSRect buttonFrame = [button frame];
   1794     buttonFrame.origin.y += bookmarks::kBookmarkFolderButtonHeight;
   1795     [button setFrame:buttonFrame];
   1796   }
   1797   [[button cell] mouseExited:nil];  // De-highlight.
   1798   BookmarkButton* newButton = [self makeButtonForNode:node
   1799                                                 frame:newButtonFrame];
   1800   [buttons_ insertObject:newButton atIndex:buttonIndex];
   1801   [folderView_ addSubview:newButton];
   1802 
   1803   // Close any child folder(s) which may still be open.
   1804   [self closeBookmarkFolder:self];
   1805 
   1806   [self adjustWindowForButtonCount:[buttons_ count]];
   1807 }
   1808 
   1809 // More code which essentially duplicates that of BookmarkBarController.
   1810 // TODO(mrossetti,jrg): http://crbug.com/35966
   1811 - (BOOL)addURLs:(NSArray*)urls withTitles:(NSArray*)titles at:(NSPoint)point {
   1812   DCHECK([urls count] == [titles count]);
   1813   BOOL nodesWereAdded = NO;
   1814   // Figure out where these new bookmarks nodes are to be added.
   1815   BookmarkButton* button = [self buttonForDroppingOnAtPoint:point];
   1816   BookmarkModel* bookmarkModel = [self bookmarkModel];
   1817   const BookmarkNode* destParent = NULL;
   1818   int destIndex = 0;
   1819   if ([button isFolder]) {
   1820     destParent = [button bookmarkNode];
   1821     // Drop it at the end.
   1822     destIndex = [button bookmarkNode]->child_count();
   1823   } else {
   1824     // Else we're dropping somewhere in the folder, so find the right spot.
   1825     destParent = [parentButton_ bookmarkNode];
   1826     destIndex = [self indexForDragToPoint:point];
   1827     // Be careful if the number of buttons != number of nodes.
   1828     destIndex += [[parentButton_ cell] startingChildIndex];
   1829   }
   1830 
   1831   // Create and add the new bookmark nodes.
   1832   size_t urlCount = [urls count];
   1833   for (size_t i = 0; i < urlCount; ++i) {
   1834     GURL gurl;
   1835     const char* string = [[urls objectAtIndex:i] UTF8String];
   1836     if (string)
   1837       gurl = GURL(string);
   1838     // We only expect to receive valid URLs.
   1839     DCHECK(gurl.is_valid());
   1840     if (gurl.is_valid()) {
   1841       bookmarkModel->AddURL(destParent,
   1842                             destIndex++,
   1843                             base::SysNSStringToUTF16([titles objectAtIndex:i]),
   1844                             gurl);
   1845       nodesWereAdded = YES;
   1846     }
   1847   }
   1848   return nodesWereAdded;
   1849 }
   1850 
   1851 - (void)moveButtonFromIndex:(NSInteger)fromIndex toIndex:(NSInteger)toIndex {
   1852   if (fromIndex != toIndex) {
   1853     if (toIndex == -1)
   1854       toIndex = [buttons_ count];
   1855     BookmarkButton* movedButton = [buttons_ objectAtIndex:fromIndex];
   1856     if (movedButton == buttonThatMouseIsIn_)
   1857       buttonThatMouseIsIn_ = nil;
   1858     [buttons_ removeObjectAtIndex:fromIndex];
   1859     NSRect movedFrame = [movedButton frame];
   1860     NSPoint toOrigin = movedFrame.origin;
   1861     [movedButton setHidden:YES];
   1862     if (fromIndex < toIndex) {
   1863       BookmarkButton* targetButton = [buttons_ objectAtIndex:toIndex - 1];
   1864       toOrigin = [targetButton frame].origin;
   1865       for (NSInteger i = fromIndex; i < toIndex; ++i) {
   1866         BookmarkButton* button = [buttons_ objectAtIndex:i];
   1867         NSRect frame = [button frame];
   1868         frame.origin.y += bookmarks::kBookmarkFolderButtonHeight;
   1869         [button setFrameOrigin:frame.origin];
   1870       }
   1871     } else {
   1872       BookmarkButton* targetButton = [buttons_ objectAtIndex:toIndex];
   1873       toOrigin = [targetButton frame].origin;
   1874       for (NSInteger i = fromIndex - 1; i >= toIndex; --i) {
   1875         BookmarkButton* button = [buttons_ objectAtIndex:i];
   1876         NSRect buttonFrame = [button frame];
   1877         buttonFrame.origin.y -= bookmarks::kBookmarkFolderButtonHeight;
   1878         [button setFrameOrigin:buttonFrame.origin];
   1879       }
   1880     }
   1881     [buttons_ insertObject:movedButton atIndex:toIndex];
   1882     [movedButton setFrameOrigin:toOrigin];
   1883     [movedButton setHidden:NO];
   1884   }
   1885 }
   1886 
   1887 // TODO(jrg): Refactor BookmarkBarFolder common code. http://crbug.com/35966
   1888 - (void)removeButton:(NSInteger)buttonIndex animate:(BOOL)animate {
   1889   // TODO(mrossetti): Get disappearing animation to work. http://crbug.com/42360
   1890   BookmarkButton* oldButton = [buttons_ objectAtIndex:buttonIndex];
   1891   NSPoint poofPoint = [oldButton screenLocationForRemoveAnimation];
   1892 
   1893   // If a hover-open is pending, cancel it.
   1894   if (oldButton == buttonThatMouseIsIn_) {
   1895     [NSObject cancelPreviousPerformRequestsWithTarget:self];
   1896     buttonThatMouseIsIn_ = nil;
   1897   }
   1898 
   1899   // Deleting a button causes rearrangement that enables us to lose a
   1900   // mouse-exited event.  This problem doesn't appear to exist with
   1901   // other keep-menu-open options (e.g. add folder).  Since the
   1902   // showsBorderOnlyWhileMouseInside uses a tracking area, simple
   1903   // tricks (e.g. sending an extra mouseExited: to the button) don't
   1904   // fix the problem.
   1905   // http://crbug.com/54324
   1906   for (NSButton* button in buttons_.get()) {
   1907     if ([button showsBorderOnlyWhileMouseInside]) {
   1908       [button setShowsBorderOnlyWhileMouseInside:NO];
   1909       [button setShowsBorderOnlyWhileMouseInside:YES];
   1910     }
   1911   }
   1912 
   1913   [oldButton setDelegate:nil];
   1914   [oldButton removeFromSuperview];
   1915   [buttons_ removeObjectAtIndex:buttonIndex];
   1916   for (NSInteger i = 0; i < buttonIndex; ++i) {
   1917     BookmarkButton* button = [buttons_ objectAtIndex:i];
   1918     NSRect buttonFrame = [button frame];
   1919     buttonFrame.origin.y -= bookmarks::kBookmarkFolderButtonHeight;
   1920     [button setFrame:buttonFrame];
   1921   }
   1922   // Search for and adjust submenus, if necessary.
   1923   NSInteger buttonCount = [buttons_ count];
   1924   if (buttonCount) {
   1925     BookmarkButton* subButton = [folderController_ parentButton];
   1926     for (NSInteger i = buttonIndex; i < buttonCount; ++i) {
   1927       BookmarkButton* aButton = [buttons_ objectAtIndex:i];
   1928       // If this button is showing its menu then we need to move the menu, too.
   1929       if (aButton == subButton)
   1930         [folderController_ offsetFolderMenuWindow:NSMakeSize(0.0,
   1931          bookmarks::kBookmarkBarHeight)];
   1932     }
   1933   } else {
   1934     // If all nodes have been removed from this folder then add in the
   1935     // 'empty' placeholder button.
   1936     NSRect buttonFrame =
   1937         NSMakeRect(0.0, 0.0, bookmarks::kDefaultBookmarkWidth,
   1938                    bookmarks::kBookmarkFolderButtonHeight);
   1939     BookmarkButton* button = [self makeButtonForNode:nil
   1940                                                frame:buttonFrame];
   1941     [buttons_ addObject:button];
   1942     [folderView_ addSubview:button];
   1943     buttonCount = 1;
   1944   }
   1945 
   1946   [self adjustWindowForButtonCount:buttonCount];
   1947 
   1948   if (animate && !ignoreAnimations_)
   1949     NSShowAnimationEffect(NSAnimationEffectDisappearingItemDefault, poofPoint,
   1950                           NSZeroSize, nil, nil, nil);
   1951 }
   1952 
   1953 - (id<BookmarkButtonControllerProtocol>)controllerForNode:
   1954     (const BookmarkNode*)node {
   1955   // See if we are holding this node, otherwise see if it is in our
   1956   // hierarchy of visible folder menus.
   1957   if ([parentButton_ bookmarkNode] == node)
   1958     return self;
   1959   return [folderController_ controllerForNode:node];
   1960 }
   1961 
   1962 #pragma mark TestingAPI Only
   1963 
   1964 - (BOOL)canScrollUp {
   1965   return ![scrollUpArrowView_ isHidden];
   1966 }
   1967 
   1968 - (BOOL)canScrollDown {
   1969   return ![scrollDownArrowView_ isHidden];
   1970 }
   1971 
   1972 - (CGFloat)verticalScrollArrowHeight {
   1973   return verticalScrollArrowHeight_;
   1974 }
   1975 
   1976 - (NSView*)visibleView {
   1977   return visibleView_;
   1978 }
   1979 
   1980 - (NSScrollView*)scrollView {
   1981   return scrollView_;
   1982 }
   1983 
   1984 - (NSView*)folderView {
   1985   return folderView_;
   1986 }
   1987 
   1988 - (void)setIgnoreAnimations:(BOOL)ignore {
   1989   ignoreAnimations_ = ignore;
   1990 }
   1991 
   1992 - (BookmarkButton*)buttonThatMouseIsIn {
   1993   return buttonThatMouseIsIn_;
   1994 }
   1995 
   1996 @end  // BookmarkBarFolderController
   1997