summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--build/common.gypi4
-rw-r--r--ui/app_list/app_list.gyp2
-rw-r--r--ui/app_list/app_list_menu.h1
-rw-r--r--ui/app_list/cocoa/app_list_view_controller.h2
-rw-r--r--ui/app_list/cocoa/app_list_view_controller.mm4
-rw-r--r--ui/app_list/cocoa/apps_search_box_controller.h10
-rw-r--r--ui/app_list/cocoa/apps_search_box_controller.mm107
-rw-r--r--ui/app_list/cocoa/apps_search_box_controller_unittest.mm64
-rw-r--r--ui/app_list/cocoa/current_user_menu_item_view.h24
-rw-r--r--ui/app_list/cocoa/current_user_menu_item_view.mm85
-rw-r--r--ui/base/cocoa/controls/hover_image_menu_button.h29
-rw-r--r--ui/base/cocoa/controls/hover_image_menu_button.mm44
-rw-r--r--ui/base/cocoa/controls/hover_image_menu_button_cell.h35
-rw-r--r--ui/base/cocoa/controls/hover_image_menu_button_cell.mm69
-rw-r--r--ui/base/cocoa/controls/hover_image_menu_button_unittest.mm173
-rw-r--r--ui/ui.gyp4
-rw-r--r--ui/ui_unittests.gypi1
17 files changed, 651 insertions, 7 deletions
diff --git a/build/common.gypi b/build/common.gypi
index ca7e6ce..051ddaa 100644
--- a/build/common.gypi
+++ b/build/common.gypi
@@ -621,8 +621,8 @@
'use_system_libjpeg%': '<(android_webview_build)',
}],
- # Enable Settings App only on Windows.
- ['enable_app_list==1 and OS=="win"', {
+ # Do not enable the Settings App on ChromeOS.
+ ['enable_app_list==1 and chromeos==0', {
'enable_settings_app%': 1,
}, {
'enable_settings_app%': 0,
diff --git a/ui/app_list/app_list.gyp b/ui/app_list/app_list.gyp
index 24d93a8..0aaa174 100644
--- a/ui/app_list/app_list.gyp
+++ b/ui/app_list/app_list.gyp
@@ -56,6 +56,8 @@
'cocoa/apps_search_results_controller.mm',
'cocoa/apps_search_results_model_bridge.h',
'cocoa/apps_search_results_model_bridge.mm',
+ 'cocoa/current_user_menu_item_view.h',
+ 'cocoa/current_user_menu_item_view.mm',
'cocoa/item_drag_controller.h',
'cocoa/item_drag_controller.mm',
'cocoa/scroll_view_with_no_scrollbars.h',
diff --git a/ui/app_list/app_list_menu.h b/ui/app_list/app_list_menu.h
index 4153f2e..ff4be49 100644
--- a/ui/app_list/app_list_menu.h
+++ b/ui/app_list/app_list_menu.h
@@ -26,7 +26,6 @@ class AppListMenu : public ui::SimpleMenuModel::Delegate {
explicit AppListMenu(AppListViewDelegate* delegate);
virtual ~AppListMenu();
- protected:
ui::SimpleMenuModel* menu_model() { return &menu_model_; }
private:
diff --git a/ui/app_list/cocoa/app_list_view_controller.h b/ui/app_list/cocoa/app_list_view_controller.h
index 4cfdb00..e0e1ad7 100644
--- a/ui/app_list/cocoa/app_list_view_controller.h
+++ b/ui/app_list/cocoa/app_list_view_controller.h
@@ -24,7 +24,7 @@ class AppListModel;
// Controller for the top-level view of the app list UI. It creates and hosts an
// AppsGridController (displaying an AppListModel), pager control to navigate
-// between pages in the grid, and search entry box.
+// between pages in the grid, and search entry box with a pop up menu.
APP_LIST_EXPORT
@interface AppListViewController : NSViewController<AppsPaginationModelObserver,
AppsSearchBoxDelegate,
diff --git a/ui/app_list/cocoa/app_list_view_controller.mm b/ui/app_list/cocoa/app_list_view_controller.mm
index eb7c5d2..50713e9 100644
--- a/ui/app_list/cocoa/app_list_view_controller.mm
+++ b/ui/app_list/cocoa/app_list_view_controller.mm
@@ -224,6 +224,10 @@ const NSTimeInterval kResultsAnimationDuration = 0.2;
return appListModel ? appListModel->search_box() : NULL;
}
+- (app_list::AppListViewDelegate*)appListDelegate {
+ return [self delegate];
+}
+
- (BOOL)control:(NSControl*)control
textView:(NSTextView*)textView
doCommandBySelector:(SEL)command {
diff --git a/ui/app_list/cocoa/apps_search_box_controller.h b/ui/app_list/cocoa/apps_search_box_controller.h
index 8f048e8..f373235 100644
--- a/ui/app_list/cocoa/apps_search_box_controller.h
+++ b/ui/app_list/cocoa/apps_search_box_controller.h
@@ -12,14 +12,19 @@
#include "ui/app_list/app_list_export.h"
namespace app_list {
+class AppListMenu;
+class AppListViewDelegate;
class SearchBoxModel;
class SearchBoxModelObserverBridge;
}
+@class AppListMenuController;
+@class HoverImageMenuButton;
@class SearchTextField;
@protocol AppsSearchBoxDelegate<NSTextFieldDelegate>
+- (app_list::AppListViewDelegate*)appListDelegate;
- (app_list::SearchBoxModel*)searchBoxModel;
- (void)modelTextDidChange;
@@ -31,7 +36,10 @@ APP_LIST_EXPORT
@private
scoped_nsobject<SearchTextField> searchTextField_;
scoped_nsobject<NSImageView> searchImageView_;
+ scoped_nsobject<HoverImageMenuButton> menuButton_;
+ scoped_nsobject<AppListMenuController> menuController_;
scoped_ptr<app_list::SearchBoxModelObserverBridge> bridge_;
+ scoped_ptr<app_list::AppListMenu> appListMenu_;
id<AppsSearchBoxDelegate> delegate_; // Weak. Owns us.
}
@@ -46,6 +54,8 @@ APP_LIST_EXPORT
@interface AppsSearchBoxController (TestingAPI)
- (NSTextField*)searchTextField;
+- (NSPopUpButton*)menuControl;
+- (app_list::AppListMenu*)appListMenu;
@end
diff --git a/ui/app_list/cocoa/apps_search_box_controller.mm b/ui/app_list/cocoa/apps_search_box_controller.mm
index 8e68425..534a064 100644
--- a/ui/app_list/cocoa/apps_search_box_controller.mm
+++ b/ui/app_list/cocoa/apps_search_box_controller.mm
@@ -6,20 +6,32 @@
#include "base/mac/foundation_util.h"
#include "base/strings/sys_string_conversions.h"
+#include "grit/ui_resources.h"
#import "third_party/GTM/AppKit/GTMNSBezierPath+RoundRect.h"
+#include "ui/app_list/app_list_menu.h"
+#import "ui/app_list/cocoa/current_user_menu_item_view.h"
#include "ui/app_list/search_box_model.h"
#include "ui/app_list/search_box_model_observer.h"
+#import "ui/base/cocoa/controls/hover_image_menu_button.h"
+#import "ui/base/cocoa/controls/hover_image_menu_button_cell.h"
+#import "ui/base/cocoa/menu_controller.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/gfx/image/image_skia_util_mac.h"
namespace {
-// Padding either side of the search icon.
+// Padding either side of the search icon and menu button.
const CGFloat kPadding = 14;
// Size of the search icon.
const CGFloat kSearchIconDimension = 32;
+// Size of the menu button on the right.
+const CGFloat kMenuButtonDimension = 29;
+
+// Vertical offset that the menu should appear below the menu button.
+const CGFloat kMenuOffsetFromButton = 2;
+
}
@interface AppsSearchBoxController ()
@@ -112,6 +124,15 @@ void SearchBoxModelObserverBridge::TextChanged() {
@end
+@interface AppListMenuController : MenuController {
+ @private
+ AppsSearchBoxController* searchBoxController_; // Weak. Owns us.
+}
+
+- (id)initWithSearchBoxController:(AppsSearchBoxController*)parent;
+
+@end
+
@implementation AppsSearchBoxController
@synthesize delegate = delegate_;
@@ -131,18 +152,36 @@ void SearchBoxModelObserverBridge::TextChanged() {
}
- (void)setDelegate:(id<AppsSearchBoxDelegate>)delegate {
+ [[menuButton_ menu] removeAllItems];
+ menuController_.reset();
+ appListMenu_.reset();
bridge_.reset(); // Ensure observers are cleared before updating |delegate_|.
delegate_ = delegate;
if (!delegate_)
return;
bridge_.reset(new app_list::SearchBoxModelObserverBridge(self));
+ if (![delegate_ appListDelegate])
+ return;
+
+ appListMenu_.reset(new app_list::AppListMenu([delegate_ appListDelegate]));
+ menuController_.reset([[AppListMenuController alloc]
+ initWithSearchBoxController:self]);
+ [menuButton_ setMenu:[menuController_ menu]]; // Menu will populate here.
}
- (NSTextField*)searchTextField {
return searchTextField_;
}
+- (NSPopUpButton*)menuControl {
+ return menuButton_;
+}
+
+- (app_list::AppListMenu*)appListMenu {
+ return appListMenu_.get();
+}
+
- (NSImageView*)searchImageView {
return searchImageView_;
}
@@ -153,15 +192,32 @@ void SearchBoxModelObserverBridge::TextChanged() {
kPadding, 0, kSearchIconDimension, NSHeight(viewBounds))]);
searchTextField_.reset([[SearchTextField alloc] initWithFrame:viewBounds]);
+ ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
[searchTextField_ setDelegate:self];
- [searchTextField_ setFont:ui::ResourceBundle::GetSharedInstance().GetFont(
+ [searchTextField_ setFont:rb.GetFont(
ui::ResourceBundle::MediumFont).GetNativeFont()];
[searchTextField_
setMarginsWithLeftMargin:NSMaxX([searchImageView_ frame]) + kPadding
- rightMargin:kPadding];
+ rightMargin:kMenuButtonDimension + 2 * kPadding];
+
+ // Add the drop-down menu, with a custom button.
+ NSRect buttonFrame = NSMakeRect(
+ NSWidth(viewBounds) - kMenuButtonDimension - kPadding,
+ floor(NSMidY(viewBounds) - kMenuButtonDimension / 2),
+ kMenuButtonDimension,
+ kMenuButtonDimension);
+ menuButton_.reset([[HoverImageMenuButton alloc] initWithFrame:buttonFrame
+ pullsDown:YES]);
+ [[menuButton_ hoverImageMenuButtonCell] setDefaultImage:
+ rb.GetNativeImageNamed(IDR_APP_LIST_TOOLS_NORMAL).AsNSImage()];
+ [[menuButton_ hoverImageMenuButtonCell] setAlternateImage:
+ rb.GetNativeImageNamed(IDR_APP_LIST_TOOLS_PRESSED).AsNSImage()];
+ [[menuButton_ hoverImageMenuButtonCell] setHoverImage:
+ rb.GetNativeImageNamed(IDR_APP_LIST_TOOLS_HOVER).AsNSImage()];
[[self view] addSubview:searchImageView_];
[[self view] addSubview:searchTextField_];
+ [[self view] addSubview:menuButton_];
}
- (BOOL)control:(NSControl*)control
@@ -291,3 +347,48 @@ void SearchBoxModelObserverBridge::TextChanged() {
}
@end
+
+@implementation AppListMenuController
+
+- (id)initWithSearchBoxController:(AppsSearchBoxController*)parent {
+ // Need to initialze super with a NULL model, otherwise it will immediately
+ // try to populate, which can't be done until setting the parent.
+ if ((self = [super initWithModel:NULL
+ useWithPopUpButtonCell:YES])) {
+ searchBoxController_ = parent;
+ [super setModel:[parent appListMenu]->menu_model()];
+ }
+ return self;
+}
+
+- (void)addItemToMenu:(NSMenu*)menu
+ atIndex:(NSInteger)index
+ fromModel:(ui::MenuModel*)model {
+ [super addItemToMenu:menu
+ atIndex:index
+ fromModel:model];
+ if (model->GetCommandIdAt(index) != app_list::AppListMenu::CURRENT_USER)
+ return;
+
+ scoped_nsobject<NSView> customItemView([[CurrentUserMenuItemView alloc]
+ initWithDelegate:[[searchBoxController_ delegate] appListDelegate]]);
+ [[menu itemAtIndex:index] setView:customItemView];
+}
+
+- (NSRect)confinementRectForMenu:(NSMenu*)menu
+ onScreen:(NSScreen*)screen {
+ NSPopUpButton* menuButton = [searchBoxController_ menuControl];
+ // Ensure the menu comes up below the menu button by trimming the window frame
+ // to a point anchored below the bottom right of the button.
+ NSRect anchorRect = [menuButton convertRect:[menuButton bounds]
+ toView:nil];
+ NSPoint anchorPoint = [[menuButton window] convertBaseToScreen:NSMakePoint(
+ NSMaxX(anchorRect),
+ NSMinY(anchorRect) - kMenuOffsetFromButton)];
+ NSRect confinementRect = [[menuButton window] frame];
+ confinementRect.size = NSMakeSize(anchorPoint.x - NSMinX(confinementRect),
+ anchorPoint.y - NSMinY(confinementRect));
+ return confinementRect;
+}
+
+@end
diff --git a/ui/app_list/cocoa/apps_search_box_controller_unittest.mm b/ui/app_list/cocoa/apps_search_box_controller_unittest.mm
index 2c75127..e2c2563 100644
--- a/ui/app_list/cocoa/apps_search_box_controller_unittest.mm
+++ b/ui/app_list/cocoa/apps_search_box_controller_unittest.mm
@@ -8,12 +8,17 @@
#include "base/strings/sys_string_conversions.h"
#include "base/strings/utf_string_conversions.h"
#import "testing/gtest_mac.h"
+#include "ui/app_list/app_list_menu.h"
+#import "ui/app_list/cocoa/current_user_menu_item_view.h"
#include "ui/app_list/search_box_model.h"
+#include "ui/app_list/test/app_list_test_model.h"
+#include "ui/app_list/test/app_list_test_view_delegate.h"
#import "ui/base/test/ui_cocoa_test_helper.h"
@interface TestAppsSearchBoxDelegate : NSObject<AppsSearchBoxDelegate> {
@private
app_list::SearchBoxModel searchBoxModel_;
+ app_list::test::AppListTestViewDelegate appListDelegate_;
int textChangeCount_;
}
@@ -29,6 +34,10 @@
return &searchBoxModel_;
}
+- (app_list::AppListViewDelegate*)appListDelegate {
+ return &appListDelegate_;
+}
+
- (BOOL)control:(NSControl*)control
textView:(NSTextView*)textView
doCommandBySelector:(SEL)command {
@@ -120,5 +129,60 @@ TEST_F(AppsSearchBoxControllerTest, SearchBoxModel) {
EXPECT_EQ(2, [delegate_ textChangeCount]);
}
+// Test the popup menu items.
+TEST_F(AppsSearchBoxControllerTest, SearchBoxMenu) {
+ NSPopUpButton* menu_control = [apps_search_box_controller_ menuControl];
+ EXPECT_TRUE([apps_search_box_controller_ appListMenu]);
+ ui::MenuModel* menu_model
+ = [apps_search_box_controller_ appListMenu]->menu_model();
+ // Add one to the item count to account for the blank, first item that Cocoa
+ // has in its popup menus.
+ EXPECT_EQ(menu_model->GetItemCount() + 1,
+ [[menu_control menu] numberOfItems]);
+
+ // The CURRENT_USER item should contain our custom view.
+ ui::MenuModel* found_menu_model = menu_model;
+ int index;
+ EXPECT_TRUE(ui::MenuModel::GetModelAndIndexForCommandId(
+ AppListMenu::CURRENT_USER, &menu_model, &index));
+ EXPECT_EQ(found_menu_model, menu_model);
+ NSMenuItem* current_user_item = [[menu_control menu] itemAtIndex:index + 1];
+ EXPECT_TRUE([current_user_item view]);
+
+ // A regular item should have just the label.
+ EXPECT_TRUE(ui::MenuModel::GetModelAndIndexForCommandId(
+ AppListMenu::SHOW_SETTINGS, &menu_model, &index));
+ EXPECT_EQ(found_menu_model, menu_model);
+ NSMenuItem* settings_item = [[menu_control menu] itemAtIndex:index + 1];
+ EXPECT_FALSE([settings_item view]);
+ EXPECT_NSEQ(base::SysUTF16ToNSString(menu_model->GetLabelAt(index)),
+ [settings_item title]);
+}
+
+// Test initialization and display of the custom menu item that shows the
+// currently signed-in user. This is a non-interactive view.
+class AppsSearchBoxCustomMenuItemTest : public ui::CocoaTest {
+ public:
+ AppsSearchBoxCustomMenuItemTest() {
+ Init();
+ }
+
+ virtual void SetUp() OVERRIDE {
+ scoped_ptr<AppListViewDelegate> delegate(new AppListTestViewDelegate);
+ current_user_menu_item_.reset([[[CurrentUserMenuItemView alloc]
+ initWithDelegate:delegate.get()] retain]);
+ ui::CocoaTest::SetUp();
+ [[test_window() contentView] addSubview:current_user_menu_item_];
+ }
+
+ protected:
+ scoped_nsobject<NSView> current_user_menu_item_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(AppsSearchBoxCustomMenuItemTest);
+};
+
+TEST_VIEW(AppsSearchBoxCustomMenuItemTest, current_user_menu_item_);
+
} // namespace test
} // namespace app_list
diff --git a/ui/app_list/cocoa/current_user_menu_item_view.h b/ui/app_list/cocoa/current_user_menu_item_view.h
new file mode 100644
index 0000000..84c240a
--- /dev/null
+++ b/ui/app_list/cocoa/current_user_menu_item_view.h
@@ -0,0 +1,24 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_APP_LIST_COCOA_CURRENT_USER_MENU_ITEM_VIEW_H_
+#define UI_APP_LIST_COCOA_CURRENT_USER_MENU_ITEM_VIEW_H_
+
+#import <Cocoa/Cocoa.h>
+
+#include "ui/app_list/app_list_export.h"
+
+namespace app_list {
+class AppListViewDelegate;
+}
+
+// The custom in-menu view representing the currently signed-in user.
+APP_LIST_EXPORT
+@interface CurrentUserMenuItemView : NSView
+
+- (id)initWithDelegate:(app_list::AppListViewDelegate*)delegate;
+
+@end
+
+#endif // UI_APP_LIST_COCOA_CURRENT_USER_MENU_ITEM_VIEW_H_
diff --git a/ui/app_list/cocoa/current_user_menu_item_view.mm b/ui/app_list/cocoa/current_user_menu_item_view.mm
new file mode 100644
index 0000000..dcc4838
--- /dev/null
+++ b/ui/app_list/cocoa/current_user_menu_item_view.mm
@@ -0,0 +1,85 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "ui/app_list/cocoa/current_user_menu_item_view.h"
+
+#include "base/logging.h"
+#include "base/memory/scoped_nsobject.h"
+#include "base/strings/sys_string_conversions.h"
+#include "grit/ui_resources.h"
+#include "ui/app_list/app_list_view_delegate.h"
+#include "ui/base/resource/resource_bundle.h"
+
+namespace {
+
+// Padding on the left of the indicator icon.
+const CGFloat kMenuLeftMargin = 3;
+
+}
+
+@interface CurrentUserMenuItemView ()
+
+// Adds a text label in the custom view in the menu showing the current user.
+- (NSTextField*)addLabelWithFrame:(NSPoint)origin
+ labelText:(const string16&)labelText;
+
+@end
+
+@implementation CurrentUserMenuItemView
+
+- (id)initWithDelegate:(app_list::AppListViewDelegate*)delegate {
+ DCHECK(delegate);
+ if ((self = [super initWithFrame:NSZeroRect])) {
+ NSImage* userImage = ui::ResourceBundle::GetSharedInstance().
+ GetNativeImageNamed(IDR_APP_LIST_USER_INDICATOR).AsNSImage();
+ NSRect imageRect = NSMakeRect(kMenuLeftMargin, 0, 0, 0);
+ imageRect.size = [userImage size];
+ scoped_nsobject<NSImageView> userImageView(
+ [[NSImageView alloc] initWithFrame:imageRect]);
+ [userImageView setImage:userImage];
+ [self addSubview:userImageView];
+
+ NSPoint labelOrigin = NSMakePoint(NSMaxX(imageRect), 0);
+ NSTextField* userField =
+ [self addLabelWithFrame:labelOrigin
+ labelText:delegate->GetCurrentUserName()];
+
+ labelOrigin.y = NSMaxY([userField frame]);
+ NSTextField* emailField =
+ [self addLabelWithFrame:labelOrigin
+ labelText:delegate->GetCurrentUserEmail()];
+ [emailField setTextColor:[NSColor disabledControlTextColor]];
+
+ // Size the container view to fit the longest label.
+ NSRect labelFrame = [emailField frame];
+ if (NSWidth([userField frame]) > NSWidth(labelFrame))
+ labelFrame.size.width = NSWidth([userField frame]);
+ [self setFrameSize:NSMakeSize(
+ NSMaxX(labelFrame) + NSMaxX(imageRect),
+ NSMaxY(labelFrame))];
+ }
+ return self;
+}
+
+- (NSTextField*)addLabelWithFrame:(NSPoint)origin
+ labelText:(const string16&)labelText {
+ NSRect labelFrame = NSZeroRect;
+ labelFrame.origin = origin;
+ scoped_nsobject<NSTextField> label(
+ [[NSTextField alloc] initWithFrame:labelFrame]);
+ [label setStringValue:base::SysUTF16ToNSString(labelText)];
+ [label setEditable:NO];
+ [label setBordered:NO];
+ [label setDrawsBackground:NO];
+ [label setFont:[NSFont menuFontOfSize:0]];
+ [label sizeToFit];
+ [self addSubview:label];
+ return label.autorelease();
+}
+
+- (BOOL)isFlipped {
+ return YES;
+}
+
+@end
diff --git a/ui/base/cocoa/controls/hover_image_menu_button.h b/ui/base/cocoa/controls/hover_image_menu_button.h
new file mode 100644
index 0000000..f6af7bf
--- /dev/null
+++ b/ui/base/cocoa/controls/hover_image_menu_button.h
@@ -0,0 +1,29 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_BASE_COCOA_CONTROLS_HOVER_IMAGE_MENU_BUTTON_H_
+#define UI_BASE_COCOA_CONTROLS_HOVER_IMAGE_MENU_BUTTON_H_
+
+#import <Cocoa/Cocoa.h>
+
+#import "ui/base/cocoa/tracking_area.h"
+#include "ui/base/ui_export.h"
+
+@class HoverImageMenuButtonCell;
+
+// An NSPopUpButton that additionally tracks mouseover state and calls
+// [[self cell] setHovered:flag] when the hover state changes. Uses
+// HoverImageMenuButtonCell as the default cellClass. Note that the menu item at
+// index 0 is ignored and client code should populate it with a dummy item.
+UI_EXPORT
+@interface HoverImageMenuButton : NSPopUpButton {
+ @private
+ ui::ScopedCrTrackingArea trackingArea_;
+}
+
+- (HoverImageMenuButtonCell*)hoverImageMenuButtonCell;
+
+@end
+
+#endif // UI_BASE_COCOA_CONTROLS_HOVER_IMAGE_MENU_BUTTON_H_
diff --git a/ui/base/cocoa/controls/hover_image_menu_button.mm b/ui/base/cocoa/controls/hover_image_menu_button.mm
new file mode 100644
index 0000000..85a3118
--- /dev/null
+++ b/ui/base/cocoa/controls/hover_image_menu_button.mm
@@ -0,0 +1,44 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "ui/base/cocoa/controls/hover_image_menu_button.h"
+
+#include "base/mac/foundation_util.h"
+#import "ui/base/cocoa/controls/hover_image_menu_button_cell.h"
+
+@implementation HoverImageMenuButton
+
++ (Class)cellClass {
+ return [HoverImageMenuButtonCell class];
+}
+
+- (id)initWithFrame:(NSRect)frameRect
+ pullsDown:(BOOL)flag {
+ if ((self = [super initWithFrame:frameRect
+ pullsDown:flag])) {
+ trackingArea_.reset(
+ [[CrTrackingArea alloc] initWithRect:NSZeroRect
+ options:NSTrackingInVisibleRect |
+ NSTrackingMouseEnteredAndExited |
+ NSTrackingActiveInKeyWindow
+ owner:self
+ userInfo:nil]);
+ [self addTrackingArea:trackingArea_.get()];
+ }
+ return self;
+}
+
+- (HoverImageMenuButtonCell*)hoverImageMenuButtonCell {
+ return base::mac::ObjCCastStrict<HoverImageMenuButtonCell>([self cell]);
+}
+
+- (void)mouseEntered:(NSEvent*)theEvent {
+ [[self cell] setHovered:YES];
+}
+
+- (void)mouseExited:(NSEvent*)theEvent {
+ [[self cell] setHovered:NO];
+}
+
+@end
diff --git a/ui/base/cocoa/controls/hover_image_menu_button_cell.h b/ui/base/cocoa/controls/hover_image_menu_button_cell.h
new file mode 100644
index 0000000..1da338b
--- /dev/null
+++ b/ui/base/cocoa/controls/hover_image_menu_button_cell.h
@@ -0,0 +1,35 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_BASE_COCOA_CONTROLS_HOVER_IMAGE_MENU_BUTTON_CELL_H_
+#define UI_BASE_COCOA_CONTROLS_HOVER_IMAGE_MENU_BUTTON_CELL_H_
+
+#import <Cocoa/Cocoa.h>
+
+#include "base/memory/scoped_nsobject.h"
+#include "ui/base/ui_export.h"
+
+// A custom NSPopUpButtonCell that permits a hover image, and draws only an
+// image in its frame; no border, bezel or drop-down arrow. Use setDefaultImage:
+// to set the default image, setAlternateImage: to set the button shown while
+// the menu is active, and setHoverImage: for the mouseover hover image.
+UI_EXPORT
+@interface HoverImageMenuButtonCell : NSPopUpButtonCell {
+ @private
+ scoped_nsobject<NSImage> hoverImage_;
+ BOOL hovered_;
+}
+
+@property(retain, nonatomic) NSImage* hoverImage;
+@property(assign, nonatomic, getter=isHovered) BOOL hovered;
+
+// Return the image that would be drawn based on the current state flags.
+- (NSImage*)imageToDraw;
+
+// Set the default image to show on the menu button.
+- (void)setDefaultImage:(NSImage*)defaultImage;
+
+@end
+
+#endif // UI_BASE_COCOA_CONTROLS_HOVER_IMAGE_MENU_BUTTON_CELL_H_
diff --git a/ui/base/cocoa/controls/hover_image_menu_button_cell.mm b/ui/base/cocoa/controls/hover_image_menu_button_cell.mm
new file mode 100644
index 0000000..32a9d69
--- /dev/null
+++ b/ui/base/cocoa/controls/hover_image_menu_button_cell.mm
@@ -0,0 +1,69 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "ui/base/cocoa/controls/hover_image_menu_button_cell.h"
+
+@implementation HoverImageMenuButtonCell
+
+@synthesize hovered = hovered_;
+
+- (id)initTextCell:(NSString*)stringValue
+ pullsDown:(BOOL)pullDown {
+ if ((self = [super initTextCell:stringValue
+ pullsDown:pullDown])) {
+ [self setUsesItemFromMenu:NO];
+ }
+ return self;
+}
+
+- (void)setHoverImage:(NSImage*)newImage {
+ if ([hoverImage_ isEqual:newImage])
+ return;
+
+ hoverImage_.reset([newImage retain]);
+ if (hovered_)
+ [[self controlView] setNeedsDisplay:YES];
+}
+
+- (NSImage*)hoverImage {
+ return hoverImage_;
+}
+
+- (void)setHovered:(BOOL)hovered {
+ if (hovered_ == hovered)
+ return;
+
+ hovered_ = hovered;
+ [[self controlView] setNeedsDisplay:YES];
+}
+
+- (NSImage*)imageToDraw {
+ if ([self isHighlighted] && [self alternateImage])
+ return [self alternateImage];
+
+ if ([self isHovered] && [self hoverImage])
+ return [self hoverImage];
+
+ // Note that NSPopUpButtonCell updates the cell image when the [self menuItem]
+ // changes.
+ return [self image];
+}
+
+- (void)setDefaultImage:(NSImage*)defaultImage {
+ scoped_nsobject<NSMenuItem> buttonMenuItem([[NSMenuItem alloc] init]);
+ [buttonMenuItem setImage:defaultImage];
+ [self setMenuItem:buttonMenuItem];
+}
+
+- (void)drawWithFrame:(NSRect)cellFrame
+ inView:(NSView*)controlView {
+ [[self imageToDraw] drawInRect:cellFrame
+ fromRect:NSZeroRect
+ operation:NSCompositeSourceOver
+ fraction:1.0
+ respectFlipped:YES
+ hints:nil];
+}
+
+@end
diff --git a/ui/base/cocoa/controls/hover_image_menu_button_unittest.mm b/ui/base/cocoa/controls/hover_image_menu_button_unittest.mm
new file mode 100644
index 0000000..45ed3689
--- /dev/null
+++ b/ui/base/cocoa/controls/hover_image_menu_button_unittest.mm
@@ -0,0 +1,173 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "ui/base/cocoa/controls/hover_image_menu_button.h"
+
+#include "base/mac/foundation_util.h"
+#import "testing/gtest_mac.h"
+#import "ui/base/cocoa/controls/hover_image_menu_button_cell.h"
+#import "ui/base/test/ui_cocoa_test_helper.h"
+
+namespace ui {
+
+namespace {
+
+// Test initialization and display of the NSPopUpButton that shows the drop-
+// down menu. Don't try to show the menu, since it will block the thread.
+class HoverImageMenuButtonTest : public CocoaTest {
+ public:
+ HoverImageMenuButtonTest() {}
+
+ // CocoaTest override:
+ virtual void SetUp() OVERRIDE;
+
+ protected:
+ scoped_nsobject<HoverImageMenuButton> menu_button_;
+ scoped_nsobject<NSImage> normal_;
+ scoped_nsobject<NSImage> pressed_;
+ scoped_nsobject<NSImage> hovered_;
+
+ DISALLOW_COPY_AND_ASSIGN(HoverImageMenuButtonTest);
+};
+
+void HoverImageMenuButtonTest::SetUp() {
+ menu_button_.reset(
+ [[HoverImageMenuButton alloc] initWithFrame:NSMakeRect(0, 0, 50, 30)
+ pullsDown:YES]);
+
+ normal_.reset([base::mac::ObjCCastStrict<NSImage>(
+ [NSImage imageNamed:NSImageNameStatusAvailable]) retain]);
+ pressed_.reset([base::mac::ObjCCastStrict<NSImage>(
+ [NSImage imageNamed:NSImageNameStatusUnavailable]) retain]);
+ hovered_.reset([base::mac::ObjCCastStrict<NSImage>(
+ [NSImage imageNamed:NSImageNameStatusPartiallyAvailable]) retain]);
+ [[menu_button_ hoverImageMenuButtonCell] setDefaultImage:normal_];
+ [[menu_button_ hoverImageMenuButtonCell] setAlternateImage:pressed_];
+ [[menu_button_ hoverImageMenuButtonCell] setHoverImage:hovered_];
+
+ CocoaTest::SetUp();
+ [[test_window() contentView] addSubview:menu_button_];
+}
+
+} // namespace
+
+TEST_VIEW(HoverImageMenuButtonTest, menu_button_);
+
+// Tests that the correct image is chosen, depending on the cell's state flags.
+TEST_F(HoverImageMenuButtonTest, CheckImagesForState) {
+ EXPECT_FALSE([[menu_button_ cell] isHovered]);
+ EXPECT_FALSE([[menu_button_ cell] isHighlighted]);
+ EXPECT_NSEQ(normal_, [[menu_button_ cell] imageToDraw]);
+ [menu_button_ display];
+
+ [[menu_button_ cell] setHovered:YES];
+ EXPECT_TRUE([[menu_button_ cell] isHovered]);
+ EXPECT_FALSE([[menu_button_ cell] isHighlighted]);
+ EXPECT_NSEQ(hovered_, [[menu_button_ cell] imageToDraw]);
+ [menu_button_ display];
+
+ // Highlighted takes precendece over hover.
+ [[menu_button_ cell] setHighlighted:YES];
+ EXPECT_TRUE([[menu_button_ cell] isHovered]);
+ EXPECT_TRUE([[menu_button_ cell] isHighlighted]);
+ EXPECT_NSEQ(pressed_, [[menu_button_ cell] imageToDraw]);
+ [menu_button_ display];
+
+ [[menu_button_ cell] setHovered:NO];
+ EXPECT_FALSE([[menu_button_ cell] isHovered]);
+ EXPECT_TRUE([[menu_button_ cell] isHighlighted]);
+ EXPECT_NSEQ(pressed_, [[menu_button_ cell] imageToDraw]);
+ [menu_button_ display];
+
+ [[menu_button_ cell] setHighlighted:NO];
+ EXPECT_FALSE([[menu_button_ cell] isHovered]);
+ EXPECT_FALSE([[menu_button_ cell] isHighlighted]);
+ EXPECT_NSEQ(normal_, [[menu_button_ cell] imageToDraw]);
+ [menu_button_ display];
+}
+
+// Tests that calling the various setXImage functions calls setNeedsDisplay.
+TEST_F(HoverImageMenuButtonTest, NewImageCausesDisplay) {
+ [menu_button_ display];
+ EXPECT_FALSE([menu_button_ needsDisplay]);
+
+ // Uses setDefaultImage rather than setImage to ensure the image goes into the
+ // NSPopUpButtonCell's menuItem. It is then accessible using [NSCell image].
+ EXPECT_NSEQ(normal_, [[menu_button_ cell] image]);
+ [[menu_button_ cell] setDefaultImage:pressed_];
+ EXPECT_NSEQ(pressed_, [[menu_button_ cell] image]);
+ EXPECT_TRUE([menu_button_ needsDisplay]);
+ [menu_button_ display];
+ EXPECT_FALSE([menu_button_ needsDisplay]);
+
+ // Highlighting the cell requires a redisplay.
+ [[menu_button_ cell] setHighlighted:YES];
+ EXPECT_TRUE([menu_button_ needsDisplay]);
+ [menu_button_ display];
+ EXPECT_FALSE([menu_button_ needsDisplay]);
+
+ // setAlternateImage comes from NSButtonCell. Ensure the added setHover*
+ // behaviour matches.
+ [[menu_button_ cell] setAlternateImage:normal_];
+ EXPECT_TRUE([menu_button_ needsDisplay]);
+ [menu_button_ display];
+ EXPECT_FALSE([menu_button_ needsDisplay]);
+
+ // Setting the same image should not cause a redisplay.
+ [[menu_button_ cell] setAlternateImage:normal_];
+ EXPECT_FALSE([menu_button_ needsDisplay]);
+
+ // Unhighlighting requires a redisplay.
+ [[menu_button_ cell] setHighlighted:NO];
+ EXPECT_TRUE([menu_button_ needsDisplay]);
+ [menu_button_ display];
+ EXPECT_FALSE([menu_button_ needsDisplay]);
+
+ // Changing hover state requires a redisplay.
+ [[menu_button_ cell] setHovered:YES];
+ EXPECT_TRUE([menu_button_ needsDisplay]);
+ [menu_button_ display];
+ EXPECT_FALSE([menu_button_ needsDisplay]);
+
+ // setHoverImage comes directly from storage in HoverImageMenuButtonCell.
+ [[menu_button_ cell] setHoverImage:normal_];
+ EXPECT_TRUE([menu_button_ needsDisplay]);
+ [menu_button_ display];
+ EXPECT_FALSE([menu_button_ needsDisplay]);
+
+ // Setting the same image should not cause a redisplay.
+ [[menu_button_ cell] setHoverImage:normal_];
+ EXPECT_FALSE([menu_button_ needsDisplay]);
+
+ // Unhover requires a redisplay.
+ [[menu_button_ cell] setHovered:NO];
+ EXPECT_TRUE([menu_button_ needsDisplay]);
+ [menu_button_ display];
+ EXPECT_FALSE([menu_button_ needsDisplay]);
+
+ // Changing the image while not hovered should not require a redisplay.
+ [[menu_button_ cell] setHoverImage:pressed_];
+ EXPECT_FALSE([menu_button_ needsDisplay]);
+}
+
+// Test that the mouse enter and exit is properly handled, to set hover state.
+TEST_F(HoverImageMenuButtonTest, SimulateMouseEnterExit) {
+ [menu_button_ display];
+ EXPECT_FALSE([menu_button_ needsDisplay]);
+ EXPECT_NSEQ(normal_, [[menu_button_ cell] imageToDraw]);
+
+ [menu_button_ mouseEntered:nil];
+ EXPECT_TRUE([menu_button_ needsDisplay]);
+ EXPECT_NSEQ(hovered_, [[menu_button_ cell] imageToDraw]);
+ [menu_button_ display];
+ EXPECT_FALSE([menu_button_ needsDisplay]);
+
+ [menu_button_ mouseExited:nil];
+ EXPECT_TRUE([menu_button_ needsDisplay]);
+ EXPECT_NSEQ(normal_, [[menu_button_ cell] imageToDraw]);
+ [menu_button_ display];
+ EXPECT_FALSE([menu_button_ needsDisplay]);
+}
+
+} // namespace ui
diff --git a/ui/ui.gyp b/ui/ui.gyp
index 0b55416..8effb69 100644
--- a/ui/ui.gyp
+++ b/ui/ui.gyp
@@ -101,6 +101,10 @@
'base/cocoa/base_view.mm',
'base/cocoa/cocoa_event_utils.h',
'base/cocoa/cocoa_event_utils.mm',
+ 'base/cocoa/controls/hover_image_menu_button.h',
+ 'base/cocoa/controls/hover_image_menu_button.mm',
+ 'base/cocoa/controls/hover_image_menu_button_cell.h',
+ 'base/cocoa/controls/hover_image_menu_button_cell.mm',
'base/cocoa/events_mac.mm',
'base/cocoa/find_pasteboard.h',
'base/cocoa/find_pasteboard.mm',
diff --git a/ui/ui_unittests.gypi b/ui/ui_unittests.gypi
index b6fa266..7735fcc 100644
--- a/ui/ui_unittests.gypi
+++ b/ui/ui_unittests.gypi
@@ -140,6 +140,7 @@
'base/clipboard/custom_data_helper_unittest.cc',
'base/cocoa/base_view_unittest.mm',
'base/cocoa/cocoa_event_utils_unittest.mm',
+ 'base/cocoa/controls/hover_image_menu_button_unittest.mm',
'base/cocoa/events_mac_unittest.mm',
'base/cocoa/focus_tracker_unittest.mm',
'base/cocoa/fullscreen_window_manager_unittest.mm',