summaryrefslogtreecommitdiffstats
path: root/chrome
diff options
context:
space:
mode:
authormaf@chromium.org <maf@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-04-19 22:59:03 +0000
committermaf@chromium.org <maf@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-04-19 22:59:03 +0000
commit6abed3d861ff00f89b7407a902c2c19e5ae60a16 (patch)
treed002b790f4bfdfb0ddfaa07bebdb304742a64af3 /chrome
parentb11953f046bbe1637041981df4a434cb96b8df30 (diff)
downloadchromium_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')
-rw-r--r--chrome/browser/ui/cocoa/bookmarks/bookmark_bar_controller.mm25
-rw-r--r--chrome/browser/ui/cocoa/bookmarks/bookmark_bar_controller_unittest.mm2
-rw-r--r--chrome/browser/ui/cocoa/bookmarks/bookmark_bar_folder_controller.h13
-rw-r--r--chrome/browser/ui/cocoa/bookmarks/bookmark_bar_folder_controller.mm202
-rw-r--r--chrome/browser/ui/cocoa/bookmarks/bookmark_bar_folder_window.mm8
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