diff options
author | maf@chromium.org <maf@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-04-19 22:59:03 +0000 |
---|---|---|
committer | maf@chromium.org <maf@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-04-19 22:59:03 +0000 |
commit | 6abed3d861ff00f89b7407a902c2c19e5ae60a16 (patch) | |
tree | d002b790f4bfdfb0ddfaa07bebdb304742a64af3 /chrome | |
parent | b11953f046bbe1637041981df4a434cb96b8df30 (diff) | |
download | chromium_src-6abed3d861ff00f89b7407a902c2c19e5ae60a16.zip chromium_src-6abed3d861ff00f89b7407a902c2c19e5ae60a16.tar.gz chromium_src-6abed3d861ff00f89b7407a902c2c19e5ae60a16.tar.bz2 |
Mac: Implement type-select and arrow key support for bookmark bar menus.
BUG=69310,69311
Review URL: http://codereview.chromium.org/6877026
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@82180 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome')
5 files changed, 243 insertions, 7 deletions
diff --git a/chrome/browser/ui/cocoa/bookmarks/bookmark_bar_controller.mm b/chrome/browser/ui/cocoa/bookmarks/bookmark_bar_controller.mm index 1a64ebb..ce7e1c0 100644 --- a/chrome/browser/ui/cocoa/bookmarks/bookmark_bar_controller.mm +++ b/chrome/browser/ui/cocoa/bookmarks/bookmark_bar_controller.mm @@ -405,8 +405,8 @@ void RecordAppLaunch(Profile* profile, GURL url) { name:NSWindowWillCloseNotification object:[[self view] window]]; [defaultCenter addObserver:self - selector:@selector(parentWindowDidResignKey:) - name:NSWindowDidResignKeyNotification + selector:@selector(parentWindowDidResignMain:) + name:NSWindowDidResignMainNotification object:[[self view] window]]; } @@ -433,7 +433,7 @@ void RecordAppLaunch(Profile* profile, GURL url) { } // NSNotificationCenter callback. -- (void)parentWindowDidResignKey:(NSNotification*)notification { +- (void)parentWindowDidResignMain:(NSNotification*)notification { [self closeFolderAndStopTrackingMenus]; } @@ -1783,10 +1783,23 @@ void RecordAppLaunch(Profile* profile, GURL url) { return YES; } break; - case NSKeyDown: + case NSKeyDown: { + bool result = NO; + // 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 (folderController_) { + result = [folderController_ handleInputText:[event characters]]; + } + } + return result; + } case NSKeyUp: - // Any key press ends things. - return YES; + return NO; case NSLeftMouseDragged: // We can get here with the following sequence: // - open a bookmark folder diff --git a/chrome/browser/ui/cocoa/bookmarks/bookmark_bar_controller_unittest.mm b/chrome/browser/ui/cocoa/bookmarks/bookmark_bar_controller_unittest.mm index 50c0b12..f0b4bce 100644 --- a/chrome/browser/ui/cocoa/bookmarks/bookmark_bar_controller_unittest.mm +++ b/chrome/browser/ui/cocoa/bookmarks/bookmark_bar_controller_unittest.mm @@ -1463,7 +1463,7 @@ TEST_F(BookmarkBarControllerTest, EventToExitCheck) { charactersIgnoringModifiers:@"x" isARepeat:NO keyCode:87]; - EXPECT_TRUE([bar_ isEventAnExitEvent:event]); + EXPECT_FALSE([bar_ isEventAnExitEvent:event]); [[[bar_ view] window] removeChildWindow:folderWindow]; } diff --git a/chrome/browser/ui/cocoa/bookmarks/bookmark_bar_folder_controller.h b/chrome/browser/ui/cocoa/bookmarks/bookmark_bar_folder_controller.h index 4079d7a..fae30ab 100644 --- a/chrome/browser/ui/cocoa/bookmarks/bookmark_bar_folder_controller.h +++ b/chrome/browser/ui/cocoa/bookmarks/bookmark_bar_folder_controller.h @@ -141,6 +141,9 @@ // Set to YES to prevent any node animations. Useful for unit testing so that // incomplete animations do not cause valgrind complaints. BOOL ignoreAnimations_; + + int selectedIndex_; + NSString* typedPrefix_; } // Designated initializer. @@ -151,10 +154,20 @@ // Return the parent button that owns the bookmark folder we represent. - (BookmarkButton*)parentButton; +// Text typed by user, for type-select and arrow key support. +// Returns YES if the menu should be closed now. +- (BOOL)handleInputText:(NSString*)newText; + +// If you wanted to clear the type-select buffer. Currently only used +// internally. +- (void)clearInputText; + // Gets notified when a fav icon asynchronously loads, so we can now use the // real icon instead of a generic placeholder. - (void)faviconLoadedForNode:(const BookmarkNode*)node; +- (void)setSelectedButtonByIndex:(int)index; + // Offset our folder menu window. This is usually needed in response to a // parent folder menu window or the bookmark bar changing position due to // the dragging of a bookmark node from the parent into this folder menu. diff --git a/chrome/browser/ui/cocoa/bookmarks/bookmark_bar_folder_controller.mm b/chrome/browser/ui/cocoa/bookmarks/bookmark_bar_folder_controller.mm index ba14668..153b42f 100644 --- a/chrome/browser/ui/cocoa/bookmarks/bookmark_bar_folder_controller.mm +++ b/chrome/browser/ui/cocoa/bookmarks/bookmark_bar_folder_controller.mm @@ -245,6 +245,7 @@ struct LayoutMetrics { ofType:@"nib"]; if ((self = [super initWithWindowNibPath:nibPath owner:self])) { parentButton_.reset([button retain]); + selectedIndex_ = -1; // We want the button to remain bordered as part of the menu path. [button forceButtonBorderToStayOnAlways:YES]; @@ -265,6 +266,8 @@ struct LayoutMetrics { } - (void)dealloc { + [self clearInputText]; + // The button is no longer part of the menu path. [parentButton_ forceButtonBorderToStayOnAlways:NO]; [parentButton_ setNeedsDisplay]; @@ -301,6 +304,10 @@ struct LayoutMetrics { [super showWindow:sender]; } +- (int)buttonCount { + return [[self buttons] count]; +} + - (BookmarkButton*)parentButton { return parentButton_.get(); } @@ -860,6 +867,8 @@ struct LayoutMetrics { // Perform a single scroll of the specified amount. - (void)performOneScroll:(CGFloat)delta { + if (delta == 0.0) + return; CGFloat finalDelta = [self determineFinalScrollDelta:delta]; if (finalDelta > 0.0 || finalDelta < 0.0) { if (buttonThatMouseIsIn_) @@ -1259,6 +1268,20 @@ static BOOL ValueInRangeInclusive(CGFloat low, CGFloat value, CGFloat high) { [self autorelease]; } +- (int)indexOfButton:(BookmarkButton*)button { + int btnCount = [self buttonCount]; + for (int i = 0 ; i < btnCount ; ++i) + if ([buttons_ objectAtIndex:i] == button) + return i; + return -1; +} + +- (BookmarkButton*)buttonAtIndex:(int)which { + if (which < 0 || which >= [self buttonCount]) + return nil; + return [buttons_ objectAtIndex:which]; +} + #pragma mark BookmarkButtonDelegate Protocol - (void)fillPasteboard:(NSPasteboard*)pboard @@ -1275,6 +1298,7 @@ static BOOL ValueInRangeInclusive(CGFloat low, CGFloat value, CGFloat high) { [[NSCursor arrowCursor] set]; buttonThatMouseIsIn_ = sender; + [self setSelectedButtonByIndex:[self indexOfButton:sender]]; // Cancel a previous hover if needed. [NSObject cancelPreviousPerformRequestsWithTarget:self]; @@ -1442,6 +1466,184 @@ static BOOL ValueInRangeInclusive(CGFloat low, CGFloat value, CGFloat high) { return ![self buttonForDroppingOnAtPoint:point]; } +// Button selection change code to support type to select and arrow key events. +#pragma mark Keyboard Support + +// Scroll the menu to show the selected button, if it's not already visible. +- (void)showSelectedButton { + int bMaxIndex = [self buttonCount] - 1; // Max array index in button array. + + // Is there a valid selected button? + if (bMaxIndex < 0 || selectedIndex_ < 0 || selectedIndex_ > bMaxIndex) + return; + + // Is the menu scrollable anyway? + if (![self canScrollUp] && ![self canScrollDown]) + return; + + // Now check to see if we need to scroll, which way, and how far. + CGFloat delta = 0.0; + NSPoint scrollPoint = [scrollView_ documentVisibleRect].origin; + CGFloat itemBottom = (bMaxIndex - selectedIndex_) * + bookmarks::kBookmarkFolderButtonHeight; + CGFloat itemTop = itemBottom + bookmarks::kBookmarkFolderButtonHeight; + CGFloat viewHeight = NSHeight([scrollView_ frame]); + + if (scrollPoint.y > itemBottom) { // Need to scroll down. + delta = scrollPoint.y - itemBottom; + } else if ((scrollPoint.y + viewHeight) < itemTop) { // Need to scroll up. + delta = -(itemTop - (scrollPoint.y + viewHeight)); + } else { // No need to scroll. + return; + } + + [self performOneScroll:delta]; +} + +// All changes to selectedness of buttons (aka fake menu items) ends up +// calling this method to actually flip the state of items. +// Needs to handle -1 as the invalid index (when nothing is selected) and +// greater than range values too. +- (void)setStateOfButtonByIndex:(int)index + state:(bool)state { + if (index < 0 || index > ([self buttonCount] -1)) + return; + + [[buttons_ objectAtIndex:index] highlight:state]; +} + +// Selects the required button and deselects the previously selected one. +// An index of -1 means no selection. +- (void)setSelectedButtonByIndex:(int)index { + if (index == selectedIndex_) + return; + + [self setStateOfButtonByIndex:selectedIndex_ state:NO]; + [self setStateOfButtonByIndex:index state:YES]; + selectedIndex_ = index; + + [self showSelectedButton]; +} + +- (void)clearInputText { + [typedPrefix_ release]; + typedPrefix_ = nil; +} + +// Find the earliest item in the folder which has the target prefix. +// Returns nil if there is no prefix or there are no matches. +// These are in no particular order, and not particularly numerous, so linear +// search should be OK. +// -1 means no match. +- (int)earliestBookmarkIndexWithPrefix:(NSString*)prefix { + if ([prefix length] == 0) // Also handles nil. + return -1; + int maxButtons = [buttons_ count]; + NSString *lowercasePrefix = [prefix lowercaseString]; + for (int i = 0 ; i < maxButtons ; ++i) { + BookmarkButton* button = [buttons_ objectAtIndex:i]; + if ([[[button title] lowercaseString] hasPrefix:lowercasePrefix]) + return i; + } + return -1; +} + +- (void)setSelectedButtonByPrefix:(NSString*)prefix { + [self setSelectedButtonByIndex:[self earliestBookmarkIndexWithPrefix:prefix]]; +} + +- (void)selectPrevious { + int newIndex; + if (selectedIndex_ == 0) + return; + if (selectedIndex_ < 0) + newIndex = [self buttonCount] -1; + else + newIndex = std::max(selectedIndex_ - 1, 0); + [self setSelectedButtonByIndex:newIndex]; +} + +- (void) selectNext { + if (selectedIndex_ + 1 < [self buttonCount]) + [self setSelectedButtonByIndex:selectedIndex_ + 1]; +} + +- (BOOL)handleInputText:(NSString*)newText { + const unichar kUnicodeEscape = 0x001B; + const unichar kUnicodeSpace = 0x0020; + + // Event goes to the deepest nested open submenu. + if (folderController_) + return [folderController_ handleInputText:newText]; + + // Look for arrow keys or other function keys. + if ([newText length] == 1) { + // Get the 16-bit unicode char. + unichar theChar = [newText characterAtIndex:0]; + switch (theChar) { + + // Keys that trigger opening of the selection. + case kUnicodeSpace: // Space. + case NSNewlineCharacter: + case NSCarriageReturnCharacter: + case NSEnterCharacter: + if (selectedIndex_ >= 0 && selectedIndex_ < [self buttonCount]) { + [self openBookmark:[buttons_ objectAtIndex:selectedIndex_]]; + return NO; // NO because the selection-handling code will close later. + } else { + return YES; // Triggering with no selection closes the menu. + } + // Keys that cancel and close the menu. + case kUnicodeEscape: + case NSDeleteCharacter: + case NSBackspaceCharacter: + [self clearInputText]; + return YES; + // Keys that change selection directionally. + case NSUpArrowFunctionKey: + [self clearInputText]; + [self selectPrevious]; + return NO; + case NSDownArrowFunctionKey: + [self clearInputText]; + [self selectNext]; + return NO; + // Keys that open and close submenus. + case NSRightArrowFunctionKey: { + BookmarkButton* btn = [self buttonAtIndex:selectedIndex_]; + if (btn && [btn isFolder]) { + [self openBookmarkFolderFromButtonAndCloseOldOne:btn]; + [folderController_ selectNext]; + } + [self clearInputText]; + return NO; + } + case NSLeftArrowFunctionKey: + [self clearInputText]; + [parentController_ closeBookmarkFolder:self]; + return NO; + + // Check for other keys that should close the menu. + default: { + if (theChar > NSUpArrowFunctionKey && + theChar <= NSModeSwitchFunctionKey) { + [self clearInputText]; + return YES; + } + break; + } + } + } + + // It is a char or string worth adding to the type-select buffer. + NSString *newString = (!typedPrefix_) ? + newText : [typedPrefix_ stringByAppendingString:newText]; + [typedPrefix_ release]; + typedPrefix_ = [newString retain]; + [self setSelectedButtonByPrefix:typedPrefix_]; + return NO; +} + // Return the y position for a drop indicator. // // TODO(jrg): again we have code dup, sort of, with diff --git a/chrome/browser/ui/cocoa/bookmarks/bookmark_bar_folder_window.mm b/chrome/browser/ui/cocoa/bookmarks/bookmark_bar_folder_window.mm index 808b796..b15c888 100644 --- a/chrome/browser/ui/cocoa/bookmarks/bookmark_bar_folder_window.mm +++ b/chrome/browser/ui/cocoa/bookmarks/bookmark_bar_folder_window.mm @@ -30,6 +30,14 @@ using bookmarks::kBookmarkBarMenuCornerRadius; return self; } +- (BOOL)canBecomeKeyWindow { + return YES; +} + +- (BOOL)canBecomeMainWindow { + return NO; +} + @end |