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_controller.h" | 5 #import "chrome/browser/ui/cocoa/bookmarks/bookmark_bar_controller.h" |
6 | 6 |
7 #include "base/mac/mac_util.h" | 7 #include "base/mac/mac_util.h" |
8 #include "base/metrics/histogram.h" | 8 #include "base/metrics/histogram.h" |
9 #include "base/sys_string_conversions.h" | 9 #include "base/sys_string_conversions.h" |
10 #include "chrome/browser/bookmarks/bookmark_editor.h" | 10 #include "chrome/browser/bookmarks/bookmark_editor.h" |
(...skipping 192 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
203 | 203 |
204 - (void)addNode:(const BookmarkNode*)child toMenu:(NSMenu*)menu; | 204 - (void)addNode:(const BookmarkNode*)child toMenu:(NSMenu*)menu; |
205 - (void)addFolderNode:(const BookmarkNode*)node toMenu:(NSMenu*)menu; | 205 - (void)addFolderNode:(const BookmarkNode*)node toMenu:(NSMenu*)menu; |
206 - (void)tagEmptyMenu:(NSMenu*)menu; | 206 - (void)tagEmptyMenu:(NSMenu*)menu; |
207 - (void)clearMenuTagMap; | 207 - (void)clearMenuTagMap; |
208 - (int)preferredHeight; | 208 - (int)preferredHeight; |
209 - (void)addNonBookmarkButtonsToView; | 209 - (void)addNonBookmarkButtonsToView; |
210 - (void)addButtonsToView; | 210 - (void)addButtonsToView; |
211 - (void)centerNoItemsLabel; | 211 - (void)centerNoItemsLabel; |
212 - (void)setNodeForBarMenu; | 212 - (void)setNodeForBarMenu; |
213 - (void)watchForExitEvent:(BOOL)watch; | |
214 - (void)resetAllButtonPositionsWithAnimation:(BOOL)animate; | 213 - (void)resetAllButtonPositionsWithAnimation:(BOOL)animate; |
215 - (BOOL)animationEnabled; | 214 - (BOOL)animationEnabled; |
216 | 215 |
217 @end | 216 @end |
218 | 217 |
219 @implementation BookmarkBarController | 218 @implementation BookmarkBarController |
220 | 219 |
221 @synthesize visualState = visualState_; | 220 @synthesize visualState = visualState_; |
222 @synthesize lastVisualState = lastVisualState_; | 221 @synthesize lastVisualState = lastVisualState_; |
223 @synthesize delegate = delegate_; | 222 @synthesize delegate = delegate_; |
(...skipping 95 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
319 | 318 |
320 // For safety, make sure the buttons can no longer call us. | 319 // For safety, make sure the buttons can no longer call us. |
321 for (BookmarkButton* button in buttons_.get()) { | 320 for (BookmarkButton* button in buttons_.get()) { |
322 [button setDelegate:nil]; | 321 [button setDelegate:nil]; |
323 [button setTarget:nil]; | 322 [button setTarget:nil]; |
324 [button setAction:nil]; | 323 [button setAction:nil]; |
325 } | 324 } |
326 | 325 |
327 bridge_.reset(NULL); | 326 bridge_.reset(NULL); |
328 [[NSNotificationCenter defaultCenter] removeObserver:self]; | 327 [[NSNotificationCenter defaultCenter] removeObserver:self]; |
329 [self watchForExitEvent:NO]; | |
330 [super dealloc]; | 328 [super dealloc]; |
331 } | 329 } |
332 | 330 |
333 - (void)awakeFromNib { | 331 - (void)awakeFromNib { |
334 // We default to NOT open, which means height=0. | 332 // We default to NOT open, which means height=0. |
335 DCHECK([[self view] isHidden]); // Hidden so it's OK to change. | 333 DCHECK([[self view] isHidden]); // Hidden so it's OK to change. |
336 | 334 |
337 // Set our initial height to zero, since that is what the superview | 335 // Set our initial height to zero, since that is what the superview |
338 // expects. We will resize ourselves open later if needed. | 336 // expects. We will resize ourselves open later if needed. |
339 [[self view] setFrame:NSMakeRect(0, 0, initialWidth_, 0)]; | 337 [[self view] setFrame:NSMakeRect(0, 0, initialWidth_, 0)]; |
(...skipping 172 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
512 node == bookmarkModel_->bookmark_bar_node() || | 510 node == bookmarkModel_->bookmark_bar_node() || |
513 node == bookmarkModel_->other_node() || | 511 node == bookmarkModel_->other_node() || |
514 node == bookmarkModel_->synced_node()) | 512 node == bookmarkModel_->synced_node()) |
515 return NO; | 513 return NO; |
516 return YES; | 514 return YES; |
517 } | 515 } |
518 | 516 |
519 #pragma mark Actions | 517 #pragma mark Actions |
520 | 518 |
521 - (IBAction)openBookmark:(id)sender { | 519 - (IBAction)openBookmark:(id)sender { |
522 BOOL isMenuItem = [[sender cell] isFolderButtonCell]; | 520 BOOL isMenuItem = [sender isFolder]; |
523 BOOL animate = isMenuItem && [self animationEnabled]; | 521 BOOL animate = isMenuItem && [self animationEnabled]; |
524 DCHECK([sender respondsToSelector:@selector(bookmarkNode)]); | 522 DCHECK([sender respondsToSelector:@selector(bookmarkNode)]); |
525 const BookmarkNode* node = [sender bookmarkNode]; | 523 const BookmarkNode* node = [sender bookmarkNode]; |
526 WindowOpenDisposition disposition = | 524 WindowOpenDisposition disposition = |
527 event_utils::WindowOpenDispositionFromNSEvent([NSApp currentEvent]); | 525 event_utils::WindowOpenDispositionFromNSEvent([NSApp currentEvent]); |
528 RecordAppLaunch(browser_->profile(), node->url()); | 526 RecordAppLaunch(browser_->profile(), node->url()); |
529 [self openURL:node->url() disposition:disposition]; | 527 [self openURL:node->url() disposition:disposition]; |
530 | 528 |
531 if (!animate) | 529 if (!animate) |
532 [self closeFolderAndStopTrackingMenus]; | 530 [self closeFolderAndStopTrackingMenus]; |
(...skipping 239 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
772 // If we just deleted the last item in an off-the-side menu so the | 770 // If we just deleted the last item in an off-the-side menu so the |
773 // button will be going away, make sure the menu goes away. | 771 // button will be going away, make sure the menu goes away. |
774 if (folderController_ && | 772 if (folderController_ && |
775 ([folderController_ parentButton] == offTheSideButton_)) | 773 ([folderController_ parentButton] == offTheSideButton_)) |
776 [self closeAllBookmarkFolders]; | 774 [self closeAllBookmarkFolders]; |
777 // (And hide the button, too.) | 775 // (And hide the button, too.) |
778 [offTheSideButton_ setHidden:YES]; | 776 [offTheSideButton_ setHidden:YES]; |
779 } | 777 } |
780 } | 778 } |
781 | 779 |
782 // Main menubar observation code, so we can know to close our fake menus if the | |
783 // user clicks on the actual menubar, as multiple unconnected menus sharing | |
784 // the screen looks weird. | |
785 // Needed because the hookForEvent method doesn't see the click on the menubar. | |
786 | |
787 // Gets called when the menubar is clicked. | |
788 - (void)begunTracking:(NSNotification *)notification { | |
789 [self closeFolderAndStopTrackingMenus]; | |
790 } | |
791 | |
792 // Install the callback. | |
793 - (void)startObservingMenubar { | |
794 NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; | |
795 [nc addObserver:self | |
796 selector:@selector(begunTracking:) | |
797 name:NSMenuDidBeginTrackingNotification | |
798 object:[NSApp mainMenu]]; | |
799 } | |
800 | |
801 // Remove the callback. | |
802 - (void)stopObservingMenubar { | |
803 NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; | |
804 [nc removeObserver:self | |
805 name:NSMenuDidBeginTrackingNotification | |
806 object:[NSApp mainMenu]]; | |
807 } | |
808 | |
809 // End of menubar observation code. | |
810 | |
811 // Begin (or end) watching for a click outside this window. Unlike | |
812 // normal NSWindows, bookmark folder "fake menu" windows do not become | |
813 // key or main. Thus, traditional notification (e.g. WillResignKey) | |
814 // won't work. Our strategy is to watch (at the app level) for a | |
815 // "click outside" these windows to detect when they logically lose | |
816 // focus. | |
817 - (void)watchForExitEvent:(BOOL)watch { | |
818 CrApplication* app = static_cast<CrApplication*>([NSApplication | |
819 sharedApplication]); | |
820 DCHECK([app isKindOfClass:[CrApplication class]]); | |
821 if (watch) { | |
822 if (!watchingForExitEvent_) { | |
823 [app addEventHook:self]; | |
824 [self startObservingMenubar]; | |
825 } | |
826 } else { | |
827 if (watchingForExitEvent_) { | |
828 [app removeEventHook:self]; | |
829 [self stopObservingMenubar]; | |
830 } | |
831 } | |
832 watchingForExitEvent_ = watch; | |
833 } | |
834 | |
835 // Keep the "no items" label centered in response to a frame size change. | 780 // Keep the "no items" label centered in response to a frame size change. |
836 - (void)centerNoItemsLabel { | 781 - (void)centerNoItemsLabel { |
837 // Note that this computation is done in the parent's coordinate system, | 782 // Note that this computation is done in the parent's coordinate system, |
838 // which is unflipped. Also, we want the label to be a fixed distance from | 783 // which is unflipped. Also, we want the label to be a fixed distance from |
839 // the bottom, so that it slides up properly (on animating to hidden). | 784 // the bottom, so that it slides up properly (on animating to hidden). |
840 // The textfield sits in the itemcontainer, so to center it we maintain | 785 // The textfield sits in the itemcontainer, so to center it we maintain |
841 // equal vertical padding on the top and bottom. | 786 // equal vertical padding on the top and bottom. |
842 int yoffset = (NSHeight([[buttonView_ noItemTextfield] frame]) - | 787 int yoffset = (NSHeight([[buttonView_ noItemTextfield] frame]) - |
843 NSHeight([[buttonView_ noItemContainer] frame])) / 2; | 788 NSHeight([[buttonView_ noItemContainer] frame])) / 2; |
844 [[buttonView_ noItemContainer] setFrameOrigin:NSMakePoint(0, yoffset)]; | 789 [[buttonView_ noItemContainer] setFrameOrigin:NSMakePoint(0, yoffset)]; |
(...skipping 851 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1696 NSColor* color = | 1641 NSColor* color = |
1697 themeProvider->GetNSColor(ThemeService::COLOR_BOOKMARK_TEXT, | 1642 themeProvider->GetNSColor(ThemeService::COLOR_BOOKMARK_TEXT, |
1698 true); | 1643 true); |
1699 for (BookmarkButton* button in buttons_.get()) { | 1644 for (BookmarkButton* button in buttons_.get()) { |
1700 BookmarkButtonCell* cell = [button cell]; | 1645 BookmarkButtonCell* cell = [button cell]; |
1701 [cell setTextColor:color]; | 1646 [cell setTextColor:color]; |
1702 } | 1647 } |
1703 [[otherBookmarksButton_ cell] setTextColor:color]; | 1648 [[otherBookmarksButton_ cell] setTextColor:color]; |
1704 } | 1649 } |
1705 | 1650 |
1706 // Return YES if the event indicates an exit from the bookmark bar | |
1707 // folder menus. E.g. "click outside" of the area we are watching. | |
1708 // At this time we are watching the area that includes all popup | |
1709 // bookmark folder windows. | |
1710 - (BOOL)isEventAnExitEvent:(NSEvent*)event { | |
1711 NSWindow* eventWindow = [event window]; | |
1712 NSWindow* myWindow = [[self view] window]; | |
1713 switch ([event type]) { | |
1714 case NSLeftMouseDown: | |
1715 case NSRightMouseDown: | |
1716 // If the click is in my window but NOT in the bookmark bar, consider | |
1717 // it a click 'outside'. Clicks directly on an active button (i.e. one | |
1718 // that is a folder and for which its folder menu is showing) are 'in'. | |
1719 // All other clicks on the bookmarks bar are counted as 'outside' | |
1720 // because they should close any open bookmark folder menu. | |
1721 if (eventWindow == myWindow) { | |
1722 NSView* hitView = | |
1723 [[eventWindow contentView] hitTest:[event locationInWindow]]; | |
1724 if (hitView == [folderController_ parentButton]) | |
1725 return NO; | |
1726 if (![hitView isDescendantOf:[self view]] || hitView == buttonView_) | |
1727 return YES; | |
1728 } | |
1729 break; | |
1730 case NSKeyDown: { | |
1731 // Event hooks often see the same keydown event twice due to the way key | |
1732 // events get dispatched and redispatched, so ignore if this keydown | |
1733 // event has the EXACT same timestamp as the previous keydown. | |
1734 static NSTimeInterval lastKeyDownEventTime; | |
1735 NSTimeInterval thisTime = [event timestamp]; | |
1736 if (lastKeyDownEventTime != thisTime) { | |
1737 lastKeyDownEventTime = thisTime; | |
1738 if ([event modifierFlags] & NSCommandKeyMask) | |
1739 return YES; | |
1740 } | |
1741 return NO; | |
1742 } | |
1743 case NSKeyUp: | |
1744 return NO; | |
1745 case NSLeftMouseDragged: | |
1746 // We can get here with the following sequence: | |
1747 // - open a bookmark folder | |
1748 // - right-click (and unclick) on it to open context menu | |
1749 // - move mouse to window titlebar then click-drag it by the titlebar | |
1750 // http://crbug.com/49333 | |
1751 return NO; | |
1752 default: | |
1753 break; | |
1754 } | |
1755 return NO; | |
1756 } | |
1757 | |
1758 #pragma mark Drag & Drop | 1651 #pragma mark Drag & Drop |
1759 | 1652 |
1760 // Find something like std::is_between<T>? I can't believe one doesn't exist. | 1653 // Find something like std::is_between<T>? I can't believe one doesn't exist. |
1761 static BOOL ValueInRangeInclusive(CGFloat low, CGFloat value, CGFloat high) { | 1654 static BOOL ValueInRangeInclusive(CGFloat low, CGFloat value, CGFloat high) { |
1762 return ((value >= low) && (value <= high)); | 1655 return ((value >= low) && (value <= high)); |
1763 } | 1656 } |
1764 | 1657 |
1765 // Return the proposed drop target for a hover open button from the | 1658 // Return the proposed drop target for a hover open button from the |
1766 // given array, or nil if none. We use this for distinguishing | 1659 // given array, or nil if none. We use this for distinguishing |
1767 // between a hover-open candidate or drop-indicator draw. | 1660 // between a hover-open candidate or drop-indicator draw. |
(...skipping 466 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2234 [button setHidden:NO]; | 2127 [button setHidden:NO]; |
2235 [self resetAllButtonPositionsWithAnimation:YES]; | 2128 [self resetAllButtonPositionsWithAnimation:YES]; |
2236 } | 2129 } |
2237 | 2130 |
2238 | 2131 |
2239 #pragma mark BookmarkButtonControllerProtocol | 2132 #pragma mark BookmarkButtonControllerProtocol |
2240 | 2133 |
2241 // Close all bookmark folders. "Folder" here is the fake menu for | 2134 // Close all bookmark folders. "Folder" here is the fake menu for |
2242 // bookmark folders, not a button context menu. | 2135 // bookmark folders, not a button context menu. |
2243 - (void)closeAllBookmarkFolders { | 2136 - (void)closeAllBookmarkFolders { |
2244 [self watchForExitEvent:NO]; | |
2245 [folderController_ closeMenu]; | 2137 [folderController_ closeMenu]; |
2246 folderController_ = nil; | 2138 folderController_ = nil; |
2247 } | 2139 } |
2248 | 2140 |
2249 - (void)closeBookmarkFolder:(id)sender { | 2141 - (void)closeBookmarkFolder:(id)sender { |
2250 // We're the top level, so close one means close them all. | 2142 // We're the top level, so close one means close them all. |
2251 [self closeAllBookmarkFolders]; | 2143 [self closeAllBookmarkFolders]; |
2252 } | 2144 } |
2253 | 2145 |
2254 - (BookmarkModel*)bookmarkModel { | 2146 - (BookmarkModel*)bookmarkModel { |
(...skipping 166 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2421 [self closeAllBookmarkFolders]; | 2313 [self closeAllBookmarkFolders]; |
2422 | 2314 |
2423 // Folder controller, like many window controllers, owns itself. | 2315 // Folder controller, like many window controllers, owns itself. |
2424 folderController_ = | 2316 folderController_ = |
2425 [[BookmarkBarFolderController alloc] initWithParentButton:parentButton | 2317 [[BookmarkBarFolderController alloc] initWithParentButton:parentButton |
2426 bookmarkModel:bookmarkModel_ | 2318 bookmarkModel:bookmarkModel_ |
2427 barController:self]; | 2319 barController:self]; |
2428 [folderController_ autorelease]; | 2320 [folderController_ autorelease]; |
2429 [folderController_ openMenu]; | 2321 [folderController_ openMenu]; |
2430 | 2322 |
2431 // Only BookmarkBarController has this; the | |
2432 // BookmarkBarFolderController does not. | |
2433 [self watchForExitEvent:YES]; | |
2434 | |
2435 // No longer need to hold the lock; the folderController_ now owns it. | 2323 // No longer need to hold the lock; the folderController_ now owns it. |
2436 [browserController releaseBarVisibilityForOwner:self | 2324 [browserController releaseBarVisibilityForOwner:self |
2437 withAnimation:NO | 2325 withAnimation:NO |
2438 delay:NO]; | 2326 delay:NO]; |
2439 } | 2327 } |
2440 | 2328 |
2441 - (void)openAll:(const BookmarkNode*)node | 2329 - (void)openAll:(const BookmarkNode*)node |
2442 disposition:(WindowOpenDisposition)disposition { | 2330 disposition:(WindowOpenDisposition)disposition { |
2443 [self closeFolderAndStopTrackingMenus]; | 2331 [self closeFolderAndStopTrackingMenus]; |
2444 bookmark_utils::OpenAll([[self view] window], | 2332 bookmark_utils::OpenAll([[self view] window], |
(...skipping 131 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2576 | 2464 |
2577 - (id<BookmarkButtonControllerProtocol>)controllerForNode: | 2465 - (id<BookmarkButtonControllerProtocol>)controllerForNode: |
2578 (const BookmarkNode*)node { | 2466 (const BookmarkNode*)node { |
2579 // See if it's in the bar, then if it is in the hierarchy of visible | 2467 // See if it's in the bar, then if it is in the hierarchy of visible |
2580 // folder menus. | 2468 // folder menus. |
2581 if (bookmarkModel_->bookmark_bar_node() == node) | 2469 if (bookmarkModel_->bookmark_bar_node() == node) |
2582 return self; | 2470 return self; |
2583 return nil; | 2471 return nil; |
2584 } | 2472 } |
2585 | 2473 |
2586 #pragma mark BookmarkButtonControllerProtocol | |
2587 | |
2588 // NOT an override of a standard Cocoa call made to NSViewControllers. | |
2589 - (void)hookForEvent:(NSEvent*)theEvent { | |
2590 if ([self isEventAnExitEvent:theEvent]) | |
2591 [self closeFolderAndStopTrackingMenus]; | |
2592 } | |
2593 | |
2594 #pragma mark TestingAPI Only | 2474 #pragma mark TestingAPI Only |
2595 | 2475 |
2596 - (NSMenu*)buttonContextMenu { | 2476 - (NSMenu*)buttonContextMenu { |
2597 return buttonContextMenu_; | 2477 return buttonContextMenu_; |
2598 } | 2478 } |
2599 | 2479 |
2600 // Intentionally ignores ownership issues; used for testing and we try | 2480 // Intentionally ignores ownership issues; used for testing and we try |
2601 // to minimize touching the object passed in (likely a mock). | 2481 // to minimize touching the object passed in (likely a mock). |
2602 - (void)setButtonContextMenu:(id)menu { | 2482 - (void)setButtonContextMenu:(id)menu { |
2603 buttonContextMenu_ = menu; | 2483 buttonContextMenu_ = menu; |
2604 } | 2484 } |
2605 | 2485 |
2606 - (void)setIgnoreAnimations:(BOOL)ignore { | 2486 - (void)setIgnoreAnimations:(BOOL)ignore { |
2607 ignoreAnimations_ = ignore; | 2487 ignoreAnimations_ = ignore; |
2608 } | 2488 } |
2609 | 2489 |
2610 @end | 2490 @end |
OLD | NEW |