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