summaryrefslogtreecommitdiffstats
path: root/ui/app_list
diff options
context:
space:
mode:
Diffstat (limited to 'ui/app_list')
-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
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