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" | |
9 #include "chrome/browser/bookmarks/bookmark_model.h" | 8 #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" | 9 #import "chrome/browser/ui/cocoa/bookmarks/bookmark_bar_controller.h" |
13 #import "chrome/browser/ui/cocoa/bookmarks/bookmark_bar_folder_button_cell.h" | 10 #import "chrome/browser/ui/cocoa/bookmarks/bookmark_button.h" |
14 #import "chrome/browser/ui/cocoa/bookmarks/bookmark_bar_folder_hover_state.h" | 11 #include "chrome/browser/ui/cocoa/bookmarks/bookmark_menu_bridge.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/bookmarks/bookmark_menu_cocoa_controller.h" | |
19 #import "chrome/browser/ui/cocoa/browser_window_controller.h" | |
20 #import "chrome/browser/ui/cocoa/event_utils.h" | |
21 #include "ui/base/theme_provider.h" | |
22 | 12 |
23 using bookmarks::kBookmarkBarMenuCornerRadius; | 13 // Forward-declare symbols that are part of the 10.6 SDK. |
| 14 #if !defined(MAC_OS_X_VERSION_10_6) || \ |
| 15 MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_6 |
24 | 16 |
25 namespace { | 17 @interface NSMenu (SnowLeopardSDK) |
26 | 18 - (BOOL)popUpMenuPositioningItem:(NSMenuItem*)item |
27 // Frequency of the scrolling timer in seconds. | 19 atLocation:(NSPoint)location |
28 const NSTimeInterval kBookmarkBarFolderScrollInterval = 0.1; | 20 inView:(NSView*)view; |
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; | |
111 @end | 21 @end |
112 | 22 |
113 @interface BookmarkBarFolderController(Private) | 23 #endif // MAC_OS_X_VERSION_10_6 |
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 | |
224 | 24 |
225 @implementation BookmarkBarFolderController | 25 @implementation BookmarkBarFolderController |
226 | 26 |
227 @synthesize subFolderGrowthToRight = subFolderGrowthToRight_; | |
228 | |
229 - (id)initWithParentButton:(BookmarkButton*)button | 27 - (id)initWithParentButton:(BookmarkButton*)button |
230 parentController:(BookmarkBarFolderController*)parentController | 28 bookmarkModel:(BookmarkModel*)model |
231 barController:(BookmarkBarController*)barController { | 29 barController:(BookmarkBarController*)barController { |
232 NSString* nibPath = | 30 if ((self = [super init])) { |
233 [base::mac::MainAppBundle() pathForResource:@"BookmarkBarFolderWindow" | |
234 ofType:@"nib"]; | |
235 if ((self = [super initWithWindowNibPath:nibPath owner:self])) { | |
236 parentButton_.reset([button retain]); | 31 parentButton_.reset([button retain]); |
237 selectedIndex_ = -1; | 32 barController_ = barController; |
238 | 33 menu_.reset([[NSMenu alloc] initWithTitle:@""]); |
239 // We want the button to remain bordered as part of the menu path. | 34 menuBridge_.reset(new BookmarkMenuBridge([parentButton_ bookmarkNode], |
240 [button forceButtonBorderToStayOnAlways:YES]; | 35 model->profile(), menu_)); |
241 | 36 [menuBridge_->controller() setDelegate:self]; |
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]); | |
253 } | 37 } |
254 return self; | 38 return self; |
255 } | 39 } |
256 | 40 |
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 | |
300 - (BookmarkButton*)parentButton { | 41 - (BookmarkButton*)parentButton { |
301 return parentButton_.get(); | 42 return parentButton_.get(); |
302 } | 43 } |
303 | 44 |
304 - (void)offsetFolderMenuWindow:(NSSize)offset { | 45 - (void)openMenu { |
305 NSWindow* window = [self window]; | 46 // Retain self so that whatever created this can forefit ownership if it |
306 NSRect windowFrame = [window frame]; | 47 // wants. This call is balanced in |-bookmarkMenuDidClose:|. |
307 windowFrame.origin.x -= offset.width; | 48 [self retain]; |
308 windowFrame.origin.y += offset.height; // Yes, in the opposite direction! | |
309 [window setFrame:windowFrame display:YES]; | |
310 [folderController_ offsetFolderMenuWindow:offset]; | |
311 } | |
312 | 49 |
313 - (void)reconfigureMenu { | 50 // If the system supports opening the menu at a specific point, do so. |
314 [NSObject cancelPreviousPerformRequestsWithTarget:self]; | 51 // Otherwise, it will be opened at the mouse event location. Eventually these |
315 for (BookmarkButton* button in buttons_.get()) { | 52 // should be switched to NSPopUpButtonCells so that this is taken care of |
316 [button setDelegate:nil]; | 53 // automatically. |
317 [button removeFromSuperview]; | 54 if ([menu_ respondsToSelector: |
318 } | 55 @selector(popUpMenuPositioningItem:atLocation:inView:)]) { |
319 [buttons_ removeAllObjects]; | 56 NSPoint point = [parentButton_ frame].origin; |
320 [self configureWindow]; | 57 point.y -= bookmarks::kBookmarkBarMenuOffset; |
321 } | 58 [menu_ popUpMenuPositioningItem:nil |
322 | 59 atLocation:point |
323 #pragma mark Private Methods | 60 inView:[parentButton_ superview]]; |
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 { | 61 } else { |
397 [button setEnabled:NO]; | 62 [NSMenu popUpContextMenu:menu_ |
398 [button setBordered:NO]; | 63 withEvent:[NSApp currentEvent] |
399 } | 64 forView:parentButton_]; |
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 } | 65 } |
643 } | 66 } |
644 | 67 |
645 - (void)adjustMetricsForMenuTopChanges:(LayoutMetrics*)layoutMetrics { | 68 - (void)closeMenu { |
646 LayoutMetrics& metrics(*layoutMetrics); | 69 NSArray* modes = [NSArray arrayWithObject:NSRunLoopCommonModes]; |
647 if (metrics.canScrollDown == metrics.couldScrollDown) { | 70 [menu_ performSelector:@selector(cancelTracking) |
648 if (!metrics.canScrollDown) { | 71 withObject:nil |
649 // Not scroll-down-able but the menu top has changed. | 72 afterDelay:0.0 |
650 metrics.deltaWindowHeight += metrics.scrollDelta; | 73 inModes:modes]; |
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 } | 74 } |
668 | 75 |
669 - (void)applyMetrics:(LayoutMetrics*)layoutMetrics { | 76 - (void)bookmarkMenuDidClose:(BookmarkMenuCocoaController*)controller { |
670 LayoutMetrics& metrics(*layoutMetrics); | 77 // Inform the bookmark bar that the folder has closed on the next iteration |
671 // Hide or show the scroll arrows. | 78 // of the event loop. If the menu was closed via a click event on a folder |
672 if (metrics.canScrollUp != metrics.couldScrollUp) | 79 // button, this message will be received before dispatching the click event |
673 [scrollUpArrowView_ setHidden:metrics.couldScrollUp]; | 80 // to the button. If the button is the same folder button that ran the menu |
674 if (metrics.canScrollDown != metrics.couldScrollDown) | 81 // in the first place, this will recursively pop open the menu because the |
675 [scrollDownArrowView_ setHidden:metrics.couldScrollDown]; | 82 // active folder will be nil-ed by |-closeBookmarkFolder:|. To prevent that, |
| 83 // perform the selector on the next iteration of the loop. |
| 84 [barController_ performSelector:@selector(closeBookmarkFolder:) |
| 85 withObject:self |
| 86 afterDelay:0.0]; |
676 | 87 |
677 // Adjust the geometry. The order is important because of sizer dependencies. | 88 // This controller is created on-demand and should be released when the menu |
678 [scrollView_ setFrame:metrics.scrollerFrame]; | 89 // closes because a new one will be created when it is opened again. |
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]; | 90 [self autorelease]; |
1311 } | 91 } |
1312 | 92 |
1313 #pragma mark BookmarkButtonDelegate Protocol | 93 @end |
1314 | 94 |
1315 - (void)fillPasteboard:(NSPasteboard*)pboard | 95 //////////////////////////////////////////////////////////////////////////////// |
1316 forDragOfButton:(BookmarkButton*)button { | |
1317 [[self folderTarget] fillPasteboard:pboard forDragOfButton:button]; | |
1318 | 96 |
1319 // Close our folder menu and submenus since we know we're going to be dragged. | 97 @implementation BookmarkBarFolderController (ExposedForTesting) |
1320 [self closeBookmarkFolder:self]; | 98 |
| 99 - (BookmarkMenuBridge*)menuBridge { |
| 100 return menuBridge_.get(); |
1321 } | 101 } |
1322 | 102 |
1323 // Called from BookmarkButton. | 103 @end |
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. | |
1358 [self retain]; | |
1359 [NSObject cancelPreviousPerformRequestsWithTarget:self]; | |
1360 [self autorelease]; | |
1361 } | |
1362 | |
1363 - (NSWindow*)browserWindow { | |
1364 return [parentController_ browserWindow]; | |
1365 } | |
1366 | |
1367 - (BOOL)canDragBookmarkButtonToTrash:(BookmarkButton*)button { | |
1368 return [barController_ canEditBookmarks] && | |
1369 [barController_ canEditBookmark:[button bookmarkNode]]; | |
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 } | |
1708 } else { | |
1709 NOTREACHED(); | |
1710 } | |
1711 | |
1712 return y; | |
1713 } | |
1714 | |
1715 - (ui::ThemeProvider*)themeProvider { | |
1716 return [parentController_ themeProvider]; | |
1717 } | |
1718 | |
1719 - (void)childFolderWillShow:(id<BookmarkButtonControllerProtocol>)child { | |
1720 // Do nothing. | |
1721 } | |
1722 | |
1723 - (void)childFolderWillClose:(id<BookmarkButtonControllerProtocol>)child { | |
1724 // Do nothing. | |
1725 } | |
1726 | |
1727 - (BookmarkBarFolderController*)folderController { | |
1728 return folderController_; | |
1729 } | |
1730 | |
1731 - (void)faviconLoadedForNode:(const BookmarkNode*)node { | |
1732 for (BookmarkButton* button in buttons_.get()) { | |
1733 if ([button bookmarkNode] == node) { | |
1734 [button setImage:[barController_ faviconForNode:node]]; | |
1735 [button setNeedsDisplay:YES]; | |
1736 return; | |
1737 } | |
1738 } | |
1739 | |
1740 // Node was not in this menu, try submenu. | |
1741 if (folderController_) | |
1742 [folderController_ faviconLoadedForNode:node]; | |
1743 } | |
1744 | |
1745 // Add a new folder controller as triggered by the given folder button. | |
1746 - (void)addNewFolderControllerWithParentButton:(BookmarkButton*)parentButton { | |
1747 if (folderController_) | |
1748 [self closeBookmarkFolder:self]; | |
1749 | |
1750 // Folder controller, like many window controllers, owns itself. | |
1751 folderController_ = | |
1752 [[BookmarkBarFolderController alloc] initWithParentButton:parentButton | |
1753 parentController:self | |
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 |