diff options
-rw-r--r-- | ui/app_list/app_list.gyp | 3 | ||||
-rw-r--r-- | ui/app_list/app_list_constants.cc | 3 | ||||
-rw-r--r-- | ui/app_list/app_list_constants.h | 3 | ||||
-rw-r--r-- | ui/app_list/cocoa/app_list_view_controller.h | 21 | ||||
-rw-r--r-- | ui/app_list/cocoa/app_list_view_controller.mm | 101 | ||||
-rw-r--r-- | ui/app_list/cocoa/app_list_view_controller_unittest.mm | 9 | ||||
-rw-r--r-- | ui/app_list/cocoa/apps_grid_controller.h | 4 | ||||
-rw-r--r-- | ui/app_list/cocoa/apps_grid_controller.mm | 7 | ||||
-rw-r--r-- | ui/app_list/cocoa/apps_grid_controller_unittest.mm | 8 | ||||
-rw-r--r-- | ui/app_list/cocoa/apps_search_box_controller.h | 52 | ||||
-rw-r--r-- | ui/app_list/cocoa/apps_search_box_controller.mm | 293 | ||||
-rw-r--r-- | ui/app_list/cocoa/apps_search_box_controller_unittest.mm | 123 | ||||
-rw-r--r-- | ui/app_list/cocoa/test/apps_grid_controller_test_helper.h | 7 | ||||
-rw-r--r-- | ui/app_list/cocoa/test/apps_grid_controller_test_helper.mm | 15 | ||||
-rw-r--r-- | ui/app_list/views/app_list_background.cc | 5 |
15 files changed, 599 insertions, 55 deletions
diff --git a/ui/app_list/app_list.gyp b/ui/app_list/app_list.gyp index b1128c1..60f21cf 100644 --- a/ui/app_list/app_list.gyp +++ b/ui/app_list/app_list.gyp @@ -50,6 +50,8 @@ 'cocoa/apps_grid_view_item.h', 'cocoa/apps_grid_view_item.mm', 'cocoa/apps_pagination_model_observer.h', + 'cocoa/apps_search_box_controller.h', + 'cocoa/apps_search_box_controller.mm', 'cocoa/item_drag_controller.h', 'cocoa/item_drag_controller.mm', 'cocoa/scroll_view_with_no_scrollbars.h', @@ -155,6 +157,7 @@ 'cocoa/app_list_view_controller_unittest.mm', 'cocoa/app_list_window_controller_unittest.mm', 'cocoa/apps_grid_controller_unittest.mm', + 'cocoa/apps_search_box_controller_unittest.mm', 'cocoa/test/apps_grid_controller_test_helper.h', 'cocoa/test/apps_grid_controller_test_helper.mm', 'views/apps_grid_view_unittest.cc', diff --git a/ui/app_list/app_list_constants.cc b/ui/app_list/app_list_constants.cc index 116dab6..a83c7dd 100644 --- a/ui/app_list/app_list_constants.cc +++ b/ui/app_list/app_list_constants.cc @@ -7,6 +7,9 @@ namespace app_list { const SkColor kContentsBackgroundColor = SkColorSetRGB(0xF5, 0xF5, 0xF5); +const SkColor kSearchBoxBackground = SK_ColorWHITE; +const SkColor kTopSeparatorColor = SkColorSetRGB(0xE5, 0xE5, 0xE5); + // 6% black over kContentsBackgroundColor const SkColor kHighlightedColor = SkColorSetRGB(0xE6, 0xE6, 0xE6); // 10% black over kContentsBackgroundColor diff --git a/ui/app_list/app_list_constants.h b/ui/app_list/app_list_constants.h index ac85f28..13d206e 100644 --- a/ui/app_list/app_list_constants.h +++ b/ui/app_list/app_list_constants.h @@ -12,6 +12,9 @@ namespace app_list { APP_LIST_EXPORT extern const SkColor kContentsBackgroundColor; +APP_LIST_EXPORT extern const SkColor kSearchBoxBackground; +APP_LIST_EXPORT extern const SkColor kTopSeparatorColor; + APP_LIST_EXPORT extern const SkColor kHighlightedColor; APP_LIST_EXPORT extern const SkColor kSelectedColor; diff --git a/ui/app_list/cocoa/app_list_view_controller.h b/ui/app_list/cocoa/app_list_view_controller.h index 80095423..40d68e3 100644 --- a/ui/app_list/cocoa/app_list_view_controller.h +++ b/ui/app_list/cocoa/app_list_view_controller.h @@ -11,23 +11,27 @@ #include "base/memory/scoped_ptr.h" #include "ui/app_list/app_list_export.h" #import "ui/app_list/cocoa/apps_pagination_model_observer.h" +#import "ui/app_list/cocoa/apps_search_box_controller.h" namespace app_list { class AppListViewDelegate; +class AppListModel; } -@class AppsGridController; @class AppListPagerView; +@class AppsGridController; // Controller for the top-level view of the app list UI. It creates and hosts an -// AppsGridController (displaying an AppListModel), and pager control for -// navigating between pages in the grid. +// AppsGridController (displaying an AppListModel), pager control to navigate +// between pages in the grid, and search entry box. APP_LIST_EXPORT -@interface AppListViewController : - NSViewController<AppsPaginationModelObserver, NSTextFieldDelegate> { +@interface AppListViewController : NSViewController<AppsPaginationModelObserver, + AppsSearchBoxDelegate> { @private scoped_nsobject<AppsGridController> appsGridController_; scoped_nsobject<AppListPagerView> pagerControl_; + scoped_nsobject<AppsSearchBoxController> appsSearchBoxController_; + scoped_nsobject<NSView> contentsView_; scoped_ptr<app_list::AppListViewDelegate> delegate_; } @@ -41,4 +45,11 @@ APP_LIST_EXPORT @end +@interface AppListViewController (TestingAPI) + +- (void)setDelegate:(scoped_ptr<app_list::AppListViewDelegate>)newDelegate + withTestModel:(scoped_ptr<app_list::AppListModel>)newModel; + +@end + #endif // UI_APP_LIST_COCOA_APP_LIST_VIEW_CONTROLLER_H_ diff --git a/ui/app_list/cocoa/app_list_view_controller.mm b/ui/app_list/cocoa/app_list_view_controller.mm index 7bfd2d5..91d82da 100644 --- a/ui/app_list/cocoa/app_list_view_controller.mm +++ b/ui/app_list/cocoa/app_list_view_controller.mm @@ -7,9 +7,12 @@ #include "base/mac/foundation_util.h" #include "skia/ext/skia_utils_mac.h" #include "ui/app_list/app_list_constants.h" +#include "ui/app_list/app_list_model.h" #include "ui/app_list/app_list_view_delegate.h" #import "ui/app_list/cocoa/app_list_pager_view.h" #import "ui/app_list/cocoa/apps_grid_controller.h" +#import "ui/base/cocoa/flipped_view.h" +#include "ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h" namespace { @@ -19,13 +22,11 @@ const CGFloat kBubbleCornerRadius = 3; // Height of the pager. const CGFloat kPagerPreferredHeight = 57; -// Padding between the top of the grid and the top of the view. -// TODO(tapted): Update padding when the search entry control is added. -const CGFloat kTopPadding = 16; +// Height of separator line drawn between the searchbox and grid view. +const CGFloat kTopSeparatorSize = 1; -// Height of the search input. TODO(tapted): Make this visible when the search -// input UI is written. -const CGFloat kSearchInputHeight = 0; +// Height of the search input. +const CGFloat kSearchInputHeight = 48; // Minimum margin on either side of the pager. If the pager grows beyond this, // the segment size is reduced. @@ -35,23 +36,29 @@ const CGFloat kMaxSegmentWidth = 80; } // namespace -@interface BackgroundView : NSView; +@interface BackgroundView : FlippedView; @end @implementation BackgroundView - (void)drawRect:(NSRect)dirtyRect { - [NSGraphicsContext saveGraphicsState]; - [gfx::SkColorToCalibratedNSColor(app_list::kContentsBackgroundColor) set]; - [[NSBezierPath bezierPathWithRoundedRect:[self bounds] + gfx::ScopedNSGraphicsContextSaveGState context; + NSRect boundsRect = [self bounds]; + NSRect searchAreaRect = NSMakeRect(0, 0, + NSWidth(boundsRect), kSearchInputHeight); + NSRect separatorRect = NSMakeRect(0, NSMaxY(searchAreaRect), + NSWidth(boundsRect), kTopSeparatorSize); + + [[NSBezierPath bezierPathWithRoundedRect:boundsRect xRadius:kBubbleCornerRadius yRadius:kBubbleCornerRadius] addClip]; - NSRectFill([self bounds]); - [NSGraphicsContext restoreGraphicsState]; -} -- (BOOL)isFlipped { - return YES; + [gfx::SkColorToCalibratedNSColor(app_list::kContentsBackgroundColor) set]; + NSRectFill(boundsRect); + [gfx::SkColorToCalibratedNSColor(app_list::kSearchBoxBackground) set]; + NSRectFill(searchAreaRect); + [gfx::SkColorToCalibratedNSColor(app_list::kTopSeparatorColor) set]; + NSRectFill(separatorRect); } @end @@ -97,9 +104,22 @@ const CGFloat kMaxSegmentWidth = 80; return delegate_.get(); } -- (void)setDelegate:(scoped_ptr<app_list::AppListViewDelegate>)newDelegate { +- (void)setDelegate:(scoped_ptr<app_list::AppListViewDelegate>)newDelegate + withTestModel:(scoped_ptr<app_list::AppListModel>)newModel { + if (delegate_) { + // First clean up, in reverse order. + [appsSearchBoxController_ setDelegate:nil]; + } delegate_.reset(newDelegate.release()); [appsGridController_ setDelegate:delegate_.get()]; + if (newModel.get()) + [appsGridController_ setModel:newModel.Pass()]; + [appsSearchBoxController_ setDelegate:self]; +} + +- (void)setDelegate:(scoped_ptr<app_list::AppListViewDelegate>)newDelegate { + [self setDelegate:newDelegate.Pass() + withTestModel:scoped_ptr<app_list::AppListModel>()]; } -(void)loadAndSetView { @@ -107,22 +127,23 @@ const CGFloat kMaxSegmentWidth = 80; [pagerControl_ setTarget:appsGridController_]; [pagerControl_ setAction:@selector(onPagerClicked:)]; - [[appsGridController_ view] setFrameOrigin:NSMakePoint(0, kTopPadding)]; + NSRect gridFrame = [[appsGridController_ view] frame]; + NSRect contentsRect = NSMakeRect(0, kSearchInputHeight + kTopSeparatorSize, + NSWidth(gridFrame), NSHeight(gridFrame) + kPagerPreferredHeight - + [AppsGridController scrollerPadding]); - NSRect backgroundRect = [[appsGridController_ view] bounds]; - backgroundRect.size.height += kPagerPreferredHeight; + contentsView_.reset([[FlippedView alloc] initWithFrame:contentsRect]); scoped_nsobject<BackgroundView> backgroundView( - [[BackgroundView alloc] initWithFrame:backgroundRect]); - - NSRect searchInputRect = - NSMakeRect(0, 0, backgroundRect.size.width, kSearchInputHeight); - scoped_nsobject<NSTextField> searchInput( - [[NSTextField alloc] initWithFrame:searchInputRect]); - [searchInput setDelegate:self]; - - [backgroundView addSubview:[appsGridController_ view]]; - [backgroundView addSubview:pagerControl_]; - [backgroundView addSubview:searchInput]; + [[BackgroundView alloc] initWithFrame: + NSMakeRect(0, 0, NSMaxX(contentsRect), NSMaxY(contentsRect))]); + appsSearchBoxController_.reset( + [[AppsSearchBoxController alloc] initWithFrame: + NSMakeRect(0, 0, NSWidth(contentsRect), kSearchInputHeight)]); + + [contentsView_ addSubview:[appsGridController_ view]]; + [contentsView_ addSubview:pagerControl_]; + [backgroundView addSubview:contentsView_]; + [backgroundView addSubview:[appsSearchBoxController_ view]]; [self setView:backgroundView]; } @@ -130,7 +151,7 @@ const CGFloat kMaxSegmentWidth = 80; size_t pageCount = [appsGridController_ pageCount]; [pagerControl_ setSegmentCount:pageCount]; - NSRect viewFrame = [[self view] bounds]; + NSRect viewFrame = [[pagerControl_ superview] bounds]; CGFloat segmentWidth = std::min( kMaxSegmentWidth, (viewFrame.size.width - 2 * kMinPagerMargin) / pageCount); @@ -163,9 +184,17 @@ const CGFloat kMaxSegmentWidth = 80; return [pagerControl_ findAndHighlightSegmentAtLocation:locationInWindow]; } +- (app_list::SearchBoxModel*)searchBoxModel { + app_list::AppListModel* appListModel = [appsGridController_ model]; + return appListModel ? appListModel->search_box() : NULL; +} + - (BOOL)control:(NSControl*)control textView:(NSTextView*)textView doCommandBySelector:(SEL)command { + // TODO(tapted): If showing search results, first pass up/down navigation to + // the search results controller. + // If anything has been written, let the search view handle it. if ([[control stringValue] length] > 0) return NO; @@ -183,4 +212,14 @@ const CGFloat kMaxSegmentWidth = 80; return [appsGridController_ handleCommandBySelector:command]; } +- (void)modelTextDidChange { + app_list::SearchBoxModel* searchBoxModel = [self searchBoxModel]; + if (!searchBoxModel || !delegate_) + return; + + // TODO(tapted): If there is a non-empty query in |searchBoxModel| reveal the + // search results, and run delegate_->StartSearch(). Or, if the query is now + // empty, hide results and run delegate_->StopSearch(). +} + @end diff --git a/ui/app_list/cocoa/app_list_view_controller_unittest.mm b/ui/app_list/cocoa/app_list_view_controller_unittest.mm index eb3a807..566819e 100644 --- a/ui/app_list/cocoa/app_list_view_controller_unittest.mm +++ b/ui/app_list/cocoa/app_list_view_controller_unittest.mm @@ -19,10 +19,7 @@ class AppListViewControllerTest : public AppsGridControllerTestHelper { virtual void SetUp() OVERRIDE { app_list_view_controller_.reset([[AppListViewController alloc] init]); - [app_list_view_controller_ setDelegate: - delegate_.PassAs<AppListViewDelegate>()]; SetUpWithGridController([app_list_view_controller_ appsGridController]); - [[test_window() contentView] addSubview:[app_list_view_controller_ view]]; } @@ -33,6 +30,12 @@ class AppListViewControllerTest : public AppsGridControllerTestHelper { AppsGridControllerTestHelper::TearDown(); } + virtual void ResetModel(scoped_ptr<AppListModel> new_model) OVERRIDE { + scoped_ptr<AppListViewDelegate> delegate_(new AppListTestViewDelegate); + [app_list_view_controller_ setDelegate:delegate_.Pass() + withTestModel:new_model.Pass()]; + } + protected: scoped_nsobject<AppListViewController> app_list_view_controller_; diff --git a/ui/app_list/cocoa/apps_grid_controller.h b/ui/app_list/cocoa/apps_grid_controller.h index 4fb2d6e..7db9fbf 100644 --- a/ui/app_list/cocoa/apps_grid_controller.h +++ b/ui/app_list/cocoa/apps_grid_controller.h @@ -54,6 +54,10 @@ APP_LIST_EXPORT + (void)setScrollAnimationDuration:(NSTimeInterval)duration; +// The amount the grid view has been extended to hold the sometimes present +// invisible scroller that allows for gesture scrolling. ++ (CGFloat)scrollerPadding; + - (NSCollectionView*)collectionViewAtPageIndex:(size_t)pageIndex; - (size_t)pageIndexForCollectionView:(NSCollectionView*)page; diff --git a/ui/app_list/cocoa/apps_grid_controller.mm b/ui/app_list/cocoa/apps_grid_controller.mm index 6a47ed4..6d556426 100644 --- a/ui/app_list/cocoa/apps_grid_controller.mm +++ b/ui/app_list/cocoa/apps_grid_controller.mm @@ -21,6 +21,7 @@ const int kFixedColumns = 4; const int kItemsPerPage = kFixedRows * kFixedColumns; // Padding space in pixels for fixed layout. +const CGFloat kGridTopPadding = 1; const CGFloat kLeftRightPadding = 16; const CGFloat kScrollerPadding = 16; @@ -142,6 +143,10 @@ class AppsGridDelegateBridge : public ui::ListModelObserver { g_scroll_duration = duration; } ++ (CGFloat)scrollerPadding { + return kScrollerPadding; +} + @synthesize paginationObserver = paginationObserver_; - (id)init { @@ -338,7 +343,7 @@ class AppsGridDelegateBridge : public ui::ListModelObserver { scoped_nsobject<PageContainerView> pagesContainer( [[PageContainerView alloc] initWithFrame:NSZeroRect]); - NSRect scrollFrame = NSMakeRect(0, 0, kViewWidth, + NSRect scrollFrame = NSMakeRect(0, kGridTopPadding, kViewWidth, kViewHeight + kScrollerPadding); scoped_nsobject<ScrollViewWithNoScrollbars> scrollView( [[ScrollViewWithNoScrollbars alloc] initWithFrame:scrollFrame]); diff --git a/ui/app_list/cocoa/apps_grid_controller_unittest.mm b/ui/app_list/cocoa/apps_grid_controller_unittest.mm index fe52e8c..b5876bc 100644 --- a/ui/app_list/cocoa/apps_grid_controller_unittest.mm +++ b/ui/app_list/cocoa/apps_grid_controller_unittest.mm @@ -80,9 +80,14 @@ class AppsGridControllerTest : public AppsGridControllerTestHelper { public: AppsGridControllerTest() {} + AppListTestViewDelegate* delegate() { + return owned_delegate_.get(); + } + virtual void SetUp() OVERRIDE { owned_apps_grid_controller_.reset([[AppsGridController alloc] init]); - [owned_apps_grid_controller_ setDelegate:delegate_.get()]; + owned_delegate_.reset(new AppListTestViewDelegate); + [owned_apps_grid_controller_ setDelegate:owned_delegate_.get()]; AppsGridControllerTestHelper::SetUpWithGridController( owned_apps_grid_controller_.get()); @@ -98,6 +103,7 @@ class AppsGridControllerTest : public AppsGridControllerTestHelper { private: scoped_nsobject<AppsGridController> owned_apps_grid_controller_; + scoped_ptr<AppListTestViewDelegate> owned_delegate_; DISALLOW_COPY_AND_ASSIGN(AppsGridControllerTest); }; diff --git a/ui/app_list/cocoa/apps_search_box_controller.h b/ui/app_list/cocoa/apps_search_box_controller.h new file mode 100644 index 0000000..8f048e8 --- /dev/null +++ b/ui/app_list/cocoa/apps_search_box_controller.h @@ -0,0 +1,52 @@ +// 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_APPS_SEARCH_BOX_CONTROLLER_H_ +#define UI_APP_LIST_COCOA_APPS_SEARCH_BOX_CONTROLLER_H_ + +#import <Cocoa/Cocoa.h> + +#include "base/memory/scoped_nsobject.h" +#include "base/memory/scoped_ptr.h" +#include "ui/app_list/app_list_export.h" + +namespace app_list { +class SearchBoxModel; +class SearchBoxModelObserverBridge; +} + +@class SearchTextField; + +@protocol AppsSearchBoxDelegate<NSTextFieldDelegate> + +- (app_list::SearchBoxModel*)searchBoxModel; +- (void)modelTextDidChange; + +@end + +// Controller for the search box in the topmost portion of the app list. +APP_LIST_EXPORT +@interface AppsSearchBoxController : NSViewController<NSTextFieldDelegate> { + @private + scoped_nsobject<SearchTextField> searchTextField_; + scoped_nsobject<NSImageView> searchImageView_; + scoped_ptr<app_list::SearchBoxModelObserverBridge> bridge_; + + id<AppsSearchBoxDelegate> delegate_; // Weak. Owns us. +} + +@property(assign, nonatomic) id<AppsSearchBoxDelegate> delegate; + +- (id)initWithFrame:(NSRect)frame; +- (void)clearSearch; + +@end + +@interface AppsSearchBoxController (TestingAPI) + +- (NSTextField*)searchTextField; + +@end + +#endif // UI_APP_LIST_COCOA_APPS_SEARCH_BOX_CONTROLLER_H_ diff --git a/ui/app_list/cocoa/apps_search_box_controller.mm b/ui/app_list/cocoa/apps_search_box_controller.mm new file mode 100644 index 0000000..ad35dba --- /dev/null +++ b/ui/app_list/cocoa/apps_search_box_controller.mm @@ -0,0 +1,293 @@ +// 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/apps_search_box_controller.h" + +#include "base/mac/foundation_util.h" +#include "base/strings/sys_string_conversions.h" +#import "third_party/GTM/AppKit/GTMNSBezierPath+RoundRect.h" +#include "ui/app_list/search_box_model.h" +#include "ui/app_list/search_box_model_observer.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. +const CGFloat kPadding = 14; + +// Size of the search icon. +const CGFloat kSearchIconDimension = 32; + +} + +@interface AppsSearchBoxController () + +- (NSImageView*)searchImageView; +- (void)addSubviews; + +@end + +namespace app_list { + +class SearchBoxModelObserverBridge : public SearchBoxModelObserver { + public: + SearchBoxModelObserverBridge(AppsSearchBoxController* parent); + virtual ~SearchBoxModelObserverBridge(); + + void SetSearchText(const string16& text); + + virtual void IconChanged() OVERRIDE; + virtual void HintTextChanged() OVERRIDE; + virtual void SelectionModelChanged() OVERRIDE; + virtual void TextChanged() OVERRIDE; + + private: + SearchBoxModel* GetModel(); + + AppsSearchBoxController* parent_; // Weak. Owns us. + + DISALLOW_COPY_AND_ASSIGN(SearchBoxModelObserverBridge); +}; + +SearchBoxModelObserverBridge::SearchBoxModelObserverBridge( + AppsSearchBoxController* parent) + : parent_(parent) { + IconChanged(); + HintTextChanged(); + GetModel()->AddObserver(this); +} + +SearchBoxModelObserverBridge::~SearchBoxModelObserverBridge() { + GetModel()->RemoveObserver(this); +} + +SearchBoxModel* SearchBoxModelObserverBridge::GetModel() { + SearchBoxModel* searchBoxModel = [[parent_ delegate] searchBoxModel]; + DCHECK(searchBoxModel); + return searchBoxModel; +} + +void SearchBoxModelObserverBridge::SetSearchText(const string16& text) { + SearchBoxModel* model = GetModel(); + model->RemoveObserver(this); + model->SetText(text); + // TODO(tapted): See if this should call SetSelectionModel here. + model->AddObserver(this); +} + +void SearchBoxModelObserverBridge::IconChanged() { + [[parent_ searchImageView] + setImage:gfx::NSImageFromImageSkia(GetModel()->icon())]; +} + +void SearchBoxModelObserverBridge::HintTextChanged() { + [[[parent_ searchTextField] cell] setPlaceholderString: + base::SysUTF16ToNSString(GetModel()->hint_text())]; +} + +void SearchBoxModelObserverBridge::SelectionModelChanged() { + // TODO(tapted): See if anything needs to be done here for RTL. +} + +void SearchBoxModelObserverBridge::TextChanged() { + // Currently the model text is only changed when we are not observing it, or + // it is changed in tests to establish a particular state. + [[parent_ searchTextField] + setStringValue:base::SysUTF16ToNSString(GetModel()->text())]; +} + +} // namespace app_list + +@interface SearchTextField : NSTextField { + @private + NSRect textFrameInset_; +} + +@property(readonly, nonatomic) NSRect textFrameInset; + +- (void)setMarginsWithLeftMargin:(CGFloat)leftMargin + rightMargin:(CGFloat)rightMargin; + +@end + +@implementation AppsSearchBoxController + +@synthesize delegate = delegate_; + +- (id)initWithFrame:(NSRect)frame { + if ((self = [super init])) { + scoped_nsobject<NSView> containerView([[NSView alloc] initWithFrame:frame]); + [self setView:containerView]; + [self addSubviews]; + } + return self; +} + +- (void)clearSearch { + [searchTextField_ setStringValue:@""]; + [self controlTextDidChange:nil]; +} + +- (void)setDelegate:(id<AppsSearchBoxDelegate>)delegate { + bridge_.reset(); // Ensure observers are cleared before updating |delegate_|. + delegate_ = delegate; + if (!delegate_) + return; + + bridge_.reset(new app_list::SearchBoxModelObserverBridge(self)); +} + +- (NSTextField*)searchTextField { + return searchTextField_; +} + +- (NSImageView*)searchImageView { + return searchImageView_; +} + +- (void)addSubviews { + NSRect viewBounds = [[self view] bounds]; + searchImageView_.reset([[NSImageView alloc] initWithFrame:NSMakeRect( + kPadding, 0, kSearchIconDimension, NSHeight(viewBounds))]); + + searchTextField_.reset([[SearchTextField alloc] initWithFrame:viewBounds]); + [searchTextField_ setDelegate:self]; + [searchTextField_ setFont:ui::ResourceBundle::GetSharedInstance().GetFont( + ui::ResourceBundle::MediumFont).GetNativeFont()]; + [searchTextField_ + setMarginsWithLeftMargin:NSMaxX([searchImageView_ frame]) + kPadding + rightMargin:kPadding]; + + [[self view] addSubview:searchImageView_]; + [[self view] addSubview:searchTextField_]; +} + +- (BOOL)control:(NSControl*)control + textView:(NSTextView*)textView + doCommandBySelector:(SEL)command { + // Forward the message first, to handle grid or search results navigation. + BOOL handled = [delegate_ control:control + textView:textView + doCommandBySelector:command]; + if (handled) + return YES; + + // If the delegate did not handle the escape key, it means the window was not + // dismissed because there were search results. Clear them. + if (command == @selector(complete:)) { + [self clearSearch]; + return YES; + } + + return NO; +} + +- (void)controlTextDidChange:(NSNotification*)notification { + if (bridge_) { + bridge_->SetSearchText( + base::SysNSStringToUTF16([searchTextField_ stringValue])); + } + + [delegate_ modelTextDidChange]; +} + +@end + +@interface SearchTextFieldCell : NSTextFieldCell; + +- (NSRect)textFrameForFrameInternal:(NSRect)cellFrame; + +@end + +@implementation SearchTextField + +@synthesize textFrameInset = textFrameInset_; + ++ (Class)cellClass { + return [SearchTextFieldCell class]; +} + +- (id)initWithFrame:(NSRect)theFrame { + if ((self = [super initWithFrame:theFrame])) { + [self setFocusRingType:NSFocusRingTypeNone]; + [self setDrawsBackground:NO]; + [self setBordered:NO]; + } + return self; +} + +- (void)setMarginsWithLeftMargin:(CGFloat)leftMargin + rightMargin:(CGFloat)rightMargin { + // Find the preferred height for the current text properties, and center. + NSRect viewBounds = [self bounds]; + [self sizeToFit]; + NSRect textBounds = [self bounds]; + textFrameInset_.origin.x = leftMargin; + textFrameInset_.origin.y = floor(NSMidY(viewBounds) - NSMidY(textBounds)); + textFrameInset_.size.width = leftMargin + rightMargin; + textFrameInset_.size.height = NSHeight(viewBounds) - NSHeight(textBounds); + [self setFrame:viewBounds]; +} + +@end + +@implementation SearchTextFieldCell + +- (NSRect)textFrameForFrameInternal:(NSRect)cellFrame { + SearchTextField* searchTextField = + base::mac::ObjCCastStrict<SearchTextField>([self controlView]); + NSRect insetRect = [searchTextField textFrameInset]; + cellFrame.origin.x += insetRect.origin.x; + cellFrame.origin.y += insetRect.origin.y; + cellFrame.size.width -= insetRect.size.width; + cellFrame.size.height -= insetRect.size.height; + return cellFrame; +} + +- (NSRect)textFrameForFrame:(NSRect)cellFrame { + return [self textFrameForFrameInternal:cellFrame]; +} + +- (NSRect)textCursorFrameForFrame:(NSRect)cellFrame { + return [self textFrameForFrameInternal:cellFrame]; +} + +- (void)resetCursorRect:(NSRect)cellFrame + inView:(NSView*)controlView { + [super resetCursorRect:[self textCursorFrameForFrame:cellFrame] + inView:controlView]; +} + +- (NSRect)drawingRectForBounds:(NSRect)theRect { + return [super drawingRectForBounds:[self textFrameForFrame:theRect]]; +} + +- (void)editWithFrame:(NSRect)cellFrame + inView:(NSView*)controlView + editor:(NSText*)editor + delegate:(id)delegate + event:(NSEvent*)event { + [super editWithFrame:[self textFrameForFrame:cellFrame] + inView:controlView + editor:editor + delegate:delegate + event:event]; +} + +- (void)selectWithFrame:(NSRect)cellFrame + inView:(NSView*)controlView + editor:(NSText*)editor + delegate:(id)delegate + start:(NSInteger)start + length:(NSInteger)length { + [super selectWithFrame:[self textFrameForFrame:cellFrame] + inView:controlView + editor:editor + delegate:delegate + start:start + length:length]; +} + +@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 new file mode 100644 index 0000000..ee47687 --- /dev/null +++ b/ui/app_list/cocoa/apps_search_box_controller_unittest.mm @@ -0,0 +1,123 @@ +// 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/apps_search_box_controller.h" + +#include "base/memory/scoped_nsobject.h" +#include "base/strings/sys_string_conversions.h" +#include "base/strings/utf_string_conversions.h" +#import "testing/gtest_mac.h" +#include "ui/app_list/search_box_model.h" +#import "ui/base/test/ui_cocoa_test_helper.h" + +@interface TestAppsSearchBoxDelegate : NSObject<AppsSearchBoxDelegate> { + @private + app_list::SearchBoxModel searchBoxModel_; + int textChangeCount_; +} + +@property(assign, nonatomic) int textChangeCount; + +@end + +@implementation TestAppsSearchBoxDelegate + +@synthesize textChangeCount = textChangeCount_; + +- (app_list::SearchBoxModel*)searchBoxModel { + return &searchBoxModel_; +} + +- (BOOL)control:(NSControl*)control + textView:(NSTextView*)textView + doCommandBySelector:(SEL)command { + return NO; +} + +- (void)modelTextDidChange { + ++textChangeCount_; +} + +- (CGFloat)bubbleCornerRadius { + return 3; +} + +@end + +namespace app_list { +namespace test { + +class AppsSearchBoxControllerTest : public ui::CocoaTest { + public: + AppsSearchBoxControllerTest() { + Init(); + } + + virtual void SetUp() OVERRIDE { + apps_search_box_controller_.reset( + [[AppsSearchBoxController alloc] initWithFrame: + NSMakeRect(0, 0, 400, 100)]); + delegate_.reset([[TestAppsSearchBoxDelegate alloc] init]); + [apps_search_box_controller_ setDelegate:delegate_]; + + ui::CocoaTest::SetUp(); + [[test_window() contentView] addSubview:[apps_search_box_controller_ view]]; + } + + virtual void TearDown() OVERRIDE { + [apps_search_box_controller_ setDelegate:nil]; + ui::CocoaTest::TearDown(); + } + + void SimulateKeyAction(SEL c) { + NSControl* control = [apps_search_box_controller_ searchTextField]; + [apps_search_box_controller_ control:control + textView:nil + doCommandBySelector:c]; + } + + protected: + scoped_nsobject<TestAppsSearchBoxDelegate> delegate_; + scoped_nsobject<AppsSearchBoxController> apps_search_box_controller_; + + private: + DISALLOW_COPY_AND_ASSIGN(AppsSearchBoxControllerTest); +}; + +TEST_VIEW(AppsSearchBoxControllerTest, [apps_search_box_controller_ view]); + +// Test the search box initialization, and search input and clearing. +TEST_F(AppsSearchBoxControllerTest, SearchBoxModel) { + app_list::SearchBoxModel* model = [delegate_ searchBoxModel]; + const string16 hit_text(ASCIIToUTF16("hint")); // Usually localized "Search". + model->SetHintText(hit_text); + EXPECT_NSEQ(base::SysUTF16ToNSString(hit_text), + [[[apps_search_box_controller_ searchTextField] cell] placeholderString]); + + const string16 search_text(ASCIIToUTF16("test")); + model->SetText(search_text); + EXPECT_NSEQ(base::SysUTF16ToNSString(search_text), + [[apps_search_box_controller_ searchTextField] stringValue]); + // Updates coming via the model should not notify the delegate. + EXPECT_EQ(0, [delegate_ textChangeCount]); + + // Updates from the view should update the model and notify the delegate. + [apps_search_box_controller_ clearSearch]; + EXPECT_EQ(string16(), model->text()); + EXPECT_NSEQ([NSString string], + [[apps_search_box_controller_ searchTextField] stringValue]); + EXPECT_EQ(1, [delegate_ textChangeCount]); + + // Test pressing escape clears the search. + model->SetText(search_text); + EXPECT_NSEQ(base::SysUTF16ToNSString(search_text), + [[apps_search_box_controller_ searchTextField] stringValue]); + SimulateKeyAction(@selector(complete:)); + EXPECT_NSEQ([NSString string], + [[apps_search_box_controller_ searchTextField] stringValue]); + EXPECT_EQ(2, [delegate_ textChangeCount]); +} + +} // namespace test +} // namespace app_list diff --git a/ui/app_list/cocoa/test/apps_grid_controller_test_helper.h b/ui/app_list/cocoa/test/apps_grid_controller_test_helper.h index 90a1014..026a4cd 100644 --- a/ui/app_list/cocoa/test/apps_grid_controller_test_helper.h +++ b/ui/app_list/cocoa/test/apps_grid_controller_test_helper.h @@ -12,6 +12,9 @@ @class AppsGridController; namespace app_list { + +class AppListModel; + namespace test { class AppListTestViewDelegate; @@ -55,10 +58,10 @@ class AppsGridControllerTestHelper : public ui::CocoaTest { NSCollectionView* GetPageAt(size_t index); NSView* GetSelectedView(); - AppListTestViewDelegate* delegate(); AppListTestModel* model(); - scoped_ptr<AppListTestViewDelegate> delegate_; + virtual void ResetModel(scoped_ptr<AppListModel> model); + AppsGridController* apps_grid_controller_; private: diff --git a/ui/app_list/cocoa/test/apps_grid_controller_test_helper.mm b/ui/app_list/cocoa/test/apps_grid_controller_test_helper.mm index 1fb663b..9023c5f 100644 --- a/ui/app_list/cocoa/test/apps_grid_controller_test_helper.mm +++ b/ui/app_list/cocoa/test/apps_grid_controller_test_helper.mm @@ -20,7 +20,6 @@ const size_t AppsGridControllerTestHelper::kItemsPerPage = 16; AppsGridControllerTestHelper::AppsGridControllerTestHelper() { Init(); - delegate_.reset(new AppListTestViewDelegate); [AppsGridController setScrollAnimationDuration:0.0]; } @@ -30,8 +29,7 @@ void AppsGridControllerTestHelper::SetUpWithGridController( AppsGridController* grid_controller) { ui::CocoaTest::SetUp(); apps_grid_controller_ = grid_controller; - scoped_ptr<AppListModel> model(new AppListTestModel); - [apps_grid_controller_ setModel:model.Pass()]; + ReplaceTestModel(0); } void AppsGridControllerTestHelper::SimulateClick(NSView* view) { @@ -58,7 +56,12 @@ void AppsGridControllerTestHelper::SimulateMouseExitItemAt(size_t index) { void AppsGridControllerTestHelper::ReplaceTestModel(int item_count) { scoped_ptr<AppListTestModel> new_model(new AppListTestModel); new_model->PopulateApps(item_count); - [apps_grid_controller_ setModel:new_model.PassAs<AppListModel>()]; + ResetModel(new_model.PassAs<AppListModel>()); +} + +void AppsGridControllerTestHelper::ResetModel( + scoped_ptr<AppListModel> new_model) { + [apps_grid_controller_ setModel:new_model.Pass()]; } std::string AppsGridControllerTestHelper::GetViewContent() const { @@ -126,10 +129,6 @@ NSView* AppsGridControllerTestHelper::GetSelectedView() { return GetItemViewAt([apps_grid_controller_ selectedItemIndex]); } -AppListTestViewDelegate* AppsGridControllerTestHelper::delegate() { - return static_cast<AppListTestViewDelegate*>(delegate_.get()); -} - AppListTestModel* AppsGridControllerTestHelper::model() { return static_cast<AppListTestModel*>([apps_grid_controller_ model]); } diff --git a/ui/app_list/views/app_list_background.cc b/ui/app_list/views/app_list_background.cc index 9cff9cc..cc55b5b 100644 --- a/ui/app_list/views/app_list_background.cc +++ b/ui/app_list/views/app_list_background.cc @@ -16,10 +16,7 @@ namespace { -const SkColor kSearchBoxBackground = SK_ColorWHITE; - -// Colors and sizes of top separator between searchbox and grid view. -const SkColor kTopSeparatorColor = SkColorSetRGB(0xE5, 0xE5, 0xE5); +// Size of top separator between searchbox and grid view. const int kTopSeparatorSize = 1; } // namespace |