Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1325)

Unified Diff: chrome/browser/ui/cocoa/bookmarks/bookmark_bar_controller.mm

Issue 8141003: [Mac] Restore the old bookmark menus now that the experiment is over. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Created 9 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: chrome/browser/ui/cocoa/bookmarks/bookmark_bar_controller.mm
diff --git a/chrome/browser/ui/cocoa/bookmarks/bookmark_bar_controller.mm b/chrome/browser/ui/cocoa/bookmarks/bookmark_bar_controller.mm
index 3569a619425dd2ea41a34a923a1aa3a9d7f0e605..6740237ddea2b3f698c674dd33a8c8ac494a2a1a 100644
--- a/chrome/browser/ui/cocoa/bookmarks/bookmark_bar_controller.mm
+++ b/chrome/browser/ui/cocoa/bookmarks/bookmark_bar_controller.mm
@@ -21,6 +21,7 @@
#import "chrome/browser/ui/cocoa/background_gradient_view.h"
#import "chrome/browser/ui/cocoa/bookmarks/bookmark_bar_bridge.h"
#import "chrome/browser/ui/cocoa/bookmarks/bookmark_bar_folder_controller.h"
+#import "chrome/browser/ui/cocoa/bookmarks/bookmark_bar_folder_window.h"
#import "chrome/browser/ui/cocoa/bookmarks/bookmark_bar_toolbar_view.h"
#import "chrome/browser/ui/cocoa/bookmarks/bookmark_bar_view.h"
#import "chrome/browser/ui/cocoa/bookmarks/bookmark_button.h"
@@ -210,6 +211,7 @@ void RecordAppLaunch(Profile* profile, GURL url) {
- (void)addButtonsToView;
- (void)centerNoItemsLabel;
- (void)setNodeForBarMenu;
+- (void)watchForExitEvent:(BOOL)watch;
- (void)resetAllButtonPositionsWithAnimation:(BOOL)animate;
- (BOOL)animationEnabled;
@@ -325,6 +327,7 @@ void RecordAppLaunch(Profile* profile, GURL url) {
bridge_.reset(NULL);
[[NSNotificationCenter defaultCenter] removeObserver:self];
+ [self watchForExitEvent:NO];
[super dealloc];
}
@@ -366,6 +369,18 @@ void RecordAppLaunch(Profile* profile, GURL url) {
name:NSViewFrameDidChangeNotification
object:[self view]];
+ // Watch for things going to or from fullscreen.
+ [[NSNotificationCenter defaultCenter]
+ addObserver:self
+ selector:@selector(willEnterOrLeaveFullscreen:)
+ name:kWillEnterFullscreenNotification
+ object:nil];
+ [[NSNotificationCenter defaultCenter]
+ addObserver:self
+ selector:@selector(willEnterOrLeaveFullscreen:)
+ name:kWillLeaveFullscreenNotification
+ object:nil];
+
// Don't pass ourself along (as 'self') until our init is completely
// done. Thus, this call is (almost) last.
bridge_.reset(new BookmarkBarBridge(self, bookmarkModel_));
@@ -396,6 +411,23 @@ void RecordAppLaunch(Profile* profile, GURL url) {
object:[[self view] window]];
}
+// When going fullscreen we can run into trouble. Our view is removed
+// from the non-fullscreen window before the non-fullscreen window
+// loses key, so our parentDidResignKey: callback never gets called.
+// In addition, a bookmark folder controller needs to be autoreleased
+// (in case it's in the event chain when closed), but the release
+// implicitly needs to happen while it's connected to the original
+// (non-fullscreen) window to "unlock bar visibility". Such a
+// contract isn't honored when going fullscreen with the menu option
+// (not with the keyboard shortcut). We fake it as best we can here.
+// We have a similar problem leaving fullscreen.
+- (void)willEnterOrLeaveFullscreen:(NSNotification*)notification {
+ if (folderController_) {
+ [self childFolderWillClose:folderController_];
+ [self closeFolderAndStopTrackingMenus];
+ }
+}
+
// NSNotificationCenter callback.
- (void)parentWindowWillClose:(NSNotification*)notification {
[self closeFolderAndStopTrackingMenus];
@@ -516,9 +548,79 @@ void RecordAppLaunch(Profile* profile, GURL url) {
#pragma mark Actions
+// Helper methods called on the main thread by runMenuFlashThread.
+
+- (void)setButtonFlashStateOn:(id)sender {
+ [sender highlight:YES];
+}
+
+- (void)setButtonFlashStateOff:(id)sender {
+ [sender highlight:NO];
+}
+
+-(void)cleanupAfterMenuFlashThread:(id)sender {
+ [self closeFolderAndStopTrackingMenus];
+
+ // Items retained by doMenuFlashOnSeparateThread below.
+ [sender release];
+ [self release];
+}
+
+// End runMenuFlashThread helper methods.
+
+// This call is invoked only by doMenuFlashOnSeparateThread below.
+// It makes the selected BookmarkButton (which is masquerading as a menu item)
+// flash a few times to give confirmation feedback, then it closes the menu.
+// It spends all its time sleeping or scheduling UI work on the main thread.
+- (void)runMenuFlashThread:(id)sender {
+
+ // Check this is not running on the main thread, as it sleeps.
+ DCHECK(![NSThread isMainThread]);
+
+ // Duration of flash phases and number of flashes designed to evoke a
+ // slightly retro "more mac-like than the Mac" feel.
+ // Current Cocoa UI has a barely perceptible flash,probably because Apple
+ // doesn't fire the action til after the animation and so there's a hurry.
+ // As this code is fully asynchronous, it can take its time.
+ const float kBBOnFlashTime = 0.08;
+ const float kBBOffFlashTime = 0.08;
+ const int kBookmarkButtonMenuFlashes = 3;
+
+ for (int count = 0 ; count < kBookmarkButtonMenuFlashes ; count++) {
+ [self performSelectorOnMainThread:@selector(setButtonFlashStateOn:)
+ withObject:sender
+ waitUntilDone:NO];
+ [NSThread sleepForTimeInterval:kBBOnFlashTime];
+ [self performSelectorOnMainThread:@selector(setButtonFlashStateOff:)
+ withObject:sender
+ waitUntilDone:NO];
+ [NSThread sleepForTimeInterval:kBBOffFlashTime];
+ }
+ [self performSelectorOnMainThread:@selector(cleanupAfterMenuFlashThread:)
+ withObject:sender
+ waitUntilDone:NO];
+}
+
+// Non-blocking call which starts the process to make the selected menu item
+// flash a few times to give confirmation feedback, after which it closes the
+// menu. The item is of course actually a BookmarkButton masquerading as a menu
+// item).
+- (void)doMenuFlashOnSeparateThread:(id)sender {
+
+ // Ensure that self and sender don't go away before the animation completes.
+ // These retains are balanced in cleanupAfterMenuFlashThread above.
+ [self retain];
+ [sender retain];
+ [NSThread detachNewThreadSelector:@selector(runMenuFlashThread:)
+ toTarget:self
+ withObject:sender];
+}
+
- (IBAction)openBookmark:(id)sender {
- BOOL isMenuItem = [sender isFolder];
+ BOOL isMenuItem = [[sender cell] isFolderButtonCell];
BOOL animate = isMenuItem && [self animationEnabled];
+ if (animate)
+ [self doMenuFlashOnSeparateThread:sender];
DCHECK([sender respondsToSelector:@selector(bookmarkNode)]);
const BookmarkNode* node = [sender bookmarkNode];
WindowOpenDisposition disposition =
@@ -537,6 +639,9 @@ void RecordAppLaunch(Profile* profile, GURL url) {
showFolderMenus_ = !showFolderMenus_;
+ if (sender == offTheSideButton_)
+ [[sender cell] setStartingChildIndex:displayedButtonCount_];
+
// Toggle presentation of bar folder menus.
[folderTarget_ openBookmarkFolderFromButton:sender];
}
@@ -754,8 +859,16 @@ void RecordAppLaunch(Profile* profile, GURL url) {
// Configure the off-the-side button (e.g. specify the node range,
// check if we should enable or disable it, etc).
- (void)configureOffTheSideButtonContentsAndVisibility {
+ // If deleting a button while off-the-side is open, buttons may be
+ // promoted from off-the-side to the bar. Accomodate.
+ if (folderController_ &&
+ ([folderController_ parentButton] == offTheSideButton_)) {
+ [folderController_ reconfigureMenu];
+ }
+
+ [[offTheSideButton_ cell] setStartingChildIndex:displayedButtonCount_];
[[offTheSideButton_ cell]
- setBookmarkNode:bookmarkModel_->bookmark_bar_node()];
+ setBookmarkNode:bookmarkModel_->bookmark_bar_node()];
int bookmarkChildren = bookmarkModel_->bookmark_bar_node()->child_count();
if (bookmarkChildren > displayedButtonCount_) {
[offTheSideButton_ setHidden:NO];
@@ -770,6 +883,59 @@ void RecordAppLaunch(Profile* profile, GURL url) {
}
}
+// Main menubar observation code, so we can know to close our fake menus if the
+// user clicks on the actual menubar, as multiple unconnected menus sharing
+// the screen looks weird.
+// Needed because the hookForEvent method doesn't see the click on the menubar.
+
+// Gets called when the menubar is clicked.
+- (void)begunTracking:(NSNotification *)notification {
+ [self closeFolderAndStopTrackingMenus];
+}
+
+// Install the callback.
+- (void)startObservingMenubar {
+ NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
+ [nc addObserver:self
+ selector:@selector(begunTracking:)
+ name:NSMenuDidBeginTrackingNotification
+ object:[NSApp mainMenu]];
+}
+
+// Remove the callback.
+- (void)stopObservingMenubar {
+ NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
+ [nc removeObserver:self
+ name:NSMenuDidBeginTrackingNotification
+ object:[NSApp mainMenu]];
+}
+
+// End of menubar observation code.
+
+// Begin (or end) watching for a click outside this window. Unlike
+// normal NSWindows, bookmark folder "fake menu" windows do not become
+// key or main. Thus, traditional notification (e.g. WillResignKey)
+// won't work. Our strategy is to watch (at the app level) for a
+// "click outside" these windows to detect when they logically lose
+// focus.
+- (void)watchForExitEvent:(BOOL)watch {
+ CrApplication* app = static_cast<CrApplication*>([NSApplication
+ sharedApplication]);
+ DCHECK([app isKindOfClass:[CrApplication class]]);
+ if (watch) {
+ if (!watchingForExitEvent_) {
+ [app addEventHook:self];
+ [self startObservingMenubar];
+ }
+ } else {
+ if (watchingForExitEvent_) {
+ [app removeEventHook:self];
+ [self stopObservingMenubar];
+ }
+ }
+ watchingForExitEvent_ = watch;
+}
+
// Keep the "no items" label centered in response to a frame size change.
- (void)centerNoItemsLabel {
// Note that this computation is done in the parent's coordinate system,
@@ -1641,6 +1807,66 @@ void RecordAppLaunch(Profile* profile, GURL url) {
[[otherBookmarksButton_ cell] setTextColor:color];
}
+// Return YES if the event indicates an exit from the bookmark bar
+// folder menus. E.g. "click outside" of the area we are watching.
+// At this time we are watching the area that includes all popup
+// bookmark folder windows.
+- (BOOL)isEventAnExitEvent:(NSEvent*)event {
+ NSWindow* eventWindow = [event window];
+ NSWindow* myWindow = [[self view] window];
+ switch ([event type]) {
+ case NSLeftMouseDown:
+ case NSRightMouseDown:
+ // If the click is in my window but NOT in the bookmark bar, consider
+ // it a click 'outside'. Clicks directly on an active button (i.e. one
+ // that is a folder and for which its folder menu is showing) are 'in'.
+ // All other clicks on the bookmarks bar are counted as 'outside'
+ // because they should close any open bookmark folder menu.
+ if (eventWindow == myWindow) {
+ NSView* hitView =
+ [[eventWindow contentView] hitTest:[event locationInWindow]];
+ if (hitView == [folderController_ parentButton])
+ return NO;
+ if (![hitView isDescendantOf:[self view]] || hitView == buttonView_)
+ return YES;
+ }
+ // If a click in a bookmark bar folder window and that isn't
+ // one of my bookmark bar folders, YES is click outside.
+ if (![eventWindow isKindOfClass:[BookmarkBarFolderWindow
+ class]]) {
+ return YES;
+ }
+ break;
+ case NSKeyDown: {
+ // Event hooks often see the same keydown event twice due to the way key
+ // events get dispatched and redispatched, so ignore if this keydown
+ // event has the EXACT same timestamp as the previous keydown.
+ static NSTimeInterval lastKeyDownEventTime;
+ NSTimeInterval thisTime = [event timestamp];
+ if (lastKeyDownEventTime != thisTime) {
+ lastKeyDownEventTime = thisTime;
+ if ([event modifierFlags] & NSCommandKeyMask)
+ return YES;
+ else if (folderController_)
+ return [folderController_ handleInputText:[event characters]];
+ }
+ return NO;
+ }
+ case NSKeyUp:
+ return NO;
+ case NSLeftMouseDragged:
+ // We can get here with the following sequence:
+ // - open a bookmark folder
+ // - right-click (and unclick) on it to open context menu
+ // - move mouse to window titlebar then click-drag it by the titlebar
+ // http://crbug.com/49333
+ return NO;
+ default:
+ break;
+ }
+ return NO;
+}
+
#pragma mark Drag & Drop
// Find something like std::is_between<T>? I can't believe one doesn't exist.
@@ -1979,6 +2205,9 @@ static BOOL ValueInRangeInclusive(CGFloat low, CGFloat value, CGFloat high) {
return;
}
}
+
+ if (folderController_)
+ [folderController_ faviconLoadedForNode:node];
}
// TODO(jrg): for now this is brute force.
@@ -2087,6 +2316,16 @@ static BOOL ValueInRangeInclusive(CGFloat low, CGFloat value, CGFloat high) {
// If already opened, then we exited but re-entered the button, so do nothing.
if ([folderController_ parentButton] == sender)
return;
+ // Else open a new one if it makes sense to do so.
+ if ([sender bookmarkNode]->is_folder()) {
+ // Update |hoverButton_| so that it corresponds to the open folder.
+ hoverButton_.reset([sender retain]);
+ [folderTarget_ openBookmarkFolderFromButton:sender];
+ } else {
+ // We're over a non-folder bookmark so close any old folders.
+ [folderController_ close];
+ folderController_ = nil;
+ }
}
// BookmarkButtonDelegate protocol implementation.
@@ -2127,7 +2366,8 @@ static BOOL ValueInRangeInclusive(CGFloat low, CGFloat value, CGFloat high) {
// Close all bookmark folders. "Folder" here is the fake menu for
// bookmark folders, not a button context menu.
- (void)closeAllBookmarkFolders {
- [folderController_ closeMenu];
+ [self watchForExitEvent:NO];
+ [folderController_ close];
folderController_ = nil;
}
@@ -2182,9 +2422,17 @@ static BOOL ValueInRangeInclusive(CGFloat low, CGFloat value, CGFloat high) {
hoverButton_.reset();
}
hoverButton_.reset([button retain]);
+ DCHECK([[hoverButton_ target]
+ respondsToSelector:@selector(openBookmarkFolderFromButton:)]);
+ [[hoverButton_ target]
+ performSelector:@selector(openBookmarkFolderFromButton:)
+ withObject:hoverButton_
+ afterDelay:bookmarks::kDragHoverOpenDelay
+ inModes:[NSArray arrayWithObject:NSRunLoopCommonModes]];
}
if (!button) {
if (hoverButton_) {
+ [NSObject cancelPreviousPerformRequestsWithTarget:[hoverButton_ target]];
[[hoverButton_ target] closeBookmarkFolder:hoverButton_];
hoverButton_.reset();
}
@@ -2289,6 +2537,29 @@ static BOOL ValueInRangeInclusive(CGFloat low, CGFloat value, CGFloat high) {
return x;
}
+- (void)childFolderWillShow:(id<BookmarkButtonControllerProtocol>)child {
+ // If the bookmarkbar is not in detached mode, lock bar visibility, forcing
+ // the overlay to stay open when in fullscreen mode.
+ if (![self isInState:bookmarks::kDetachedState] &&
+ ![self isAnimatingToState:bookmarks::kDetachedState]) {
+ BrowserWindowController* browserController =
+ [BrowserWindowController browserWindowControllerForView:[self view]];
+ [browserController lockBarVisibilityForOwner:child
+ withAnimation:NO
+ delay:NO];
+ }
+}
+
+- (void)childFolderWillClose:(id<BookmarkButtonControllerProtocol>)child {
+ // Release bar visibility, allowing the overlay to close if in fullscreen
+ // mode.
+ BrowserWindowController* browserController =
+ [BrowserWindowController browserWindowControllerForView:[self view]];
+ [browserController releaseBarVisibilityForOwner:child
+ withAnimation:NO
+ delay:NO];
+}
+
// Add a new folder controller as triggered by the given folder button.
- (void)addNewFolderControllerWithParentButton:(BookmarkButton*)parentButton {
@@ -2308,15 +2579,13 @@ static BOOL ValueInRangeInclusive(CGFloat low, CGFloat value, CGFloat high) {
// Folder controller, like many window controllers, owns itself.
folderController_ =
[[BookmarkBarFolderController alloc] initWithParentButton:parentButton
- bookmarkModel:bookmarkModel_
+ parentController:nil
barController:self];
- [folderController_ autorelease];
+ [folderController_ showWindow:self];
- // If this is for the off-the-side menu, set the display count.
- if (parentButton == offTheSideButton_)
- [folderController_ setOffTheSideNodeStartIndex:displayedButtonCount_];
-
- [folderController_ openMenu];
+ // Only BookmarkBarController has this; the
+ // BookmarkBarFolderController does not.
+ [self watchForExitEvent:YES];
// No longer need to hold the lock; the folderController_ now owns it.
[browserController releaseBarVisibilityForOwner:self
@@ -2357,6 +2626,7 @@ static BOOL ValueInRangeInclusive(CGFloat low, CGFloat value, CGFloat high) {
// A button from somewhere else (not the bar) is being moved to the
// off-the-side so insure it gets redrawn if its showing.
[self reconfigureBookmarkBar];
+ [folderController_ reconfigureMenu];
}
}
@@ -2424,6 +2694,7 @@ static BOOL ValueInRangeInclusive(CGFloat low, CGFloat value, CGFloat high) {
// while possibly re-laying out the bookmark bar.
[self removeButton:fromIndex animate:NO];
[self reconfigureBookmarkBar];
+ [folderController_ reconfigureMenu];
} else if (toIndex < buttonCount) {
// A button is being added to the bar and removed from off-the-side.
// By now the node has already been inserted into the model so the
@@ -2433,6 +2704,11 @@ static BOOL ValueInRangeInclusive(CGFloat low, CGFloat value, CGFloat high) {
DCHECK(movedNode);
[self addButtonForNode:movedNode atIndex:toIndex];
[self reconfigureBookmarkBar];
+ } else {
+ // A button is being moved within the off-the-side.
+ fromIndex -= buttonCount;
+ toIndex -= buttonCount;
+ [folderController_ moveButtonFromIndex:fromIndex toIndex:toIndex];
}
}
}
@@ -2457,6 +2733,12 @@ static BOOL ValueInRangeInclusive(CGFloat low, CGFloat value, CGFloat high) {
--displayedButtonCount_;
[self resetAllButtonPositionsWithAnimation:YES];
[self reconfigureBookmarkBar];
+ } else if (folderController_ &&
+ [folderController_ parentButton] == offTheSideButton_) {
+ // The button being removed is in the OTS (off-the-side) and the OTS
+ // menu is showing so we need to remove the button.
+ NSInteger index = buttonIndex - displayedButtonCount_;
+ [folderController_ removeButton:index animate:YES];
}
}
@@ -2466,7 +2748,15 @@ static BOOL ValueInRangeInclusive(CGFloat low, CGFloat value, CGFloat high) {
// folder menus.
if (bookmarkModel_->bookmark_bar_node() == node)
return self;
- return nil;
+ return [folderController_ controllerForNode:node];
+}
+
+#pragma mark BookmarkButtonControllerProtocol
+
+// NOT an override of a standard Cocoa call made to NSViewControllers.
+- (void)hookForEvent:(NSEvent*)theEvent {
+ if ([self isEventAnExitEvent:theEvent])
+ [self closeFolderAndStopTrackingMenus];
}
#pragma mark TestingAPI Only

Powered by Google App Engine
This is Rietveld 408576698