diff options
Diffstat (limited to 'ui/app_list')
-rw-r--r-- | ui/app_list/app_list.gyp | 2 | ||||
-rw-r--r-- | ui/app_list/app_list_menu.h | 1 | ||||
-rw-r--r-- | ui/app_list/cocoa/app_list_view_controller.h | 2 | ||||
-rw-r--r-- | ui/app_list/cocoa/app_list_view_controller.mm | 4 | ||||
-rw-r--r-- | ui/app_list/cocoa/apps_search_box_controller.h | 10 | ||||
-rw-r--r-- | ui/app_list/cocoa/apps_search_box_controller.mm | 107 | ||||
-rw-r--r-- | ui/app_list/cocoa/apps_search_box_controller_unittest.mm | 64 | ||||
-rw-r--r-- | ui/app_list/cocoa/current_user_menu_item_view.h | 24 | ||||
-rw-r--r-- | ui/app_list/cocoa/current_user_menu_item_view.mm | 85 |
9 files changed, 294 insertions, 5 deletions
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 |