apps_grid_view_item.mm revision eb525c5499e34cc9c4b825d6d9e75bb07cc06ace
1// Copyright 2013 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#import "ui/app_list/cocoa/apps_grid_view_item.h" 6 7#include "base/mac/foundation_util.h" 8#include "base/mac/scoped_nsobject.h" 9#include "base/strings/sys_string_conversions.h" 10#include "skia/ext/skia_utils_mac.h" 11#include "ui/app_list/app_list_constants.h" 12#include "ui/app_list/app_list_item_model.h" 13#include "ui/app_list/app_list_item_model_observer.h" 14#import "ui/base/cocoa/menu_controller.h" 15#include "ui/base/resource/resource_bundle.h" 16#include "ui/gfx/font.h" 17#include "ui/gfx/image/image_skia_util_mac.h" 18#include "ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h" 19 20namespace { 21 22// Padding from the top of the tile to the top of the app icon. 23const CGFloat kTileTopPadding = 10; 24 25} // namespace 26 27namespace app_list { 28 29class ItemModelObserverBridge : public app_list::AppListItemModelObserver { 30 public: 31 ItemModelObserverBridge(AppsGridViewItem* parent, AppListItemModel* model); 32 virtual ~ItemModelObserverBridge(); 33 34 AppListItemModel* model() { return model_; } 35 NSMenu* GetContextMenu(); 36 37 virtual void ItemIconChanged() OVERRIDE; 38 virtual void ItemTitleChanged() OVERRIDE; 39 virtual void ItemHighlightedChanged() OVERRIDE; 40 virtual void ItemIsInstallingChanged() OVERRIDE; 41 virtual void ItemPercentDownloadedChanged() OVERRIDE; 42 43 private: 44 AppsGridViewItem* parent_; // Weak. Owns us. 45 AppListItemModel* model_; // Weak. Owned by AppListModel::Apps. 46 base::scoped_nsobject<MenuController> context_menu_controller_; 47 48 DISALLOW_COPY_AND_ASSIGN(ItemModelObserverBridge); 49}; 50 51ItemModelObserverBridge::ItemModelObserverBridge(AppsGridViewItem* parent, 52 AppListItemModel* model) 53 : parent_(parent), 54 model_(model) { 55 model_->AddObserver(this); 56} 57 58ItemModelObserverBridge::~ItemModelObserverBridge() { 59 model_->RemoveObserver(this); 60} 61 62NSMenu* ItemModelObserverBridge::GetContextMenu() { 63 if (!context_menu_controller_) { 64 context_menu_controller_.reset( 65 [[MenuController alloc] initWithModel:model_->GetContextMenuModel() 66 useWithPopUpButtonCell:NO]); 67 } 68 return [context_menu_controller_ menu]; 69} 70 71void ItemModelObserverBridge::ItemIconChanged() { 72 [[parent_ button] setImage:gfx::NSImageFromImageSkia(model_->icon())]; 73} 74 75void ItemModelObserverBridge::ItemTitleChanged() { 76 [parent_ setButtonTitle:base::SysUTF8ToNSString(model_->title())]; 77} 78 79void ItemModelObserverBridge::ItemHighlightedChanged() { 80 //TODO(tapted): Ensure the item view is visible (requires pagination). 81} 82 83void ItemModelObserverBridge::ItemIsInstallingChanged() { 84 //TODO(tapted): Hide the title while itemModel->is_installing(). 85} 86 87void ItemModelObserverBridge::ItemPercentDownloadedChanged() { 88 //TODO(tapted): Update install progress bar for this item. 89} 90 91} // namespace app_list 92 93// Container for an NSButton to allow proper alignment of the icon in the apps 94// grid, and to draw with a highlight when selected. 95@interface AppsGridItemBackgroundView : NSView { 96 @private 97 BOOL selected_; 98} 99 100- (NSButton*)button; 101 102- (void)setSelected:(BOOL)flag; 103 104@end 105 106@interface AppsGridItemButtonCell : NSButtonCell { 107 @private 108 BOOL hasShadow_; 109} 110 111@property(assign, nonatomic) BOOL hasShadow; 112 113@end 114 115@interface AppsGridItemButton : NSButton; 116@end 117 118@implementation AppsGridItemBackgroundView 119 120- (NSButton*)button { 121 return base::mac::ObjCCastStrict<NSButton>([[self subviews] objectAtIndex:0]); 122} 123 124- (void)setSelected:(BOOL)flag { 125 DCHECK(selected_ != flag); 126 selected_ = flag; 127 [self setNeedsDisplay:YES]; 128} 129 130// Ignore all hit tests. The grid controller needs to be the owner of any drags. 131- (NSView*)hitTest:(NSPoint)aPoint { 132 return nil; 133} 134 135- (void)drawRect:(NSRect)dirtyRect { 136 if (!selected_) 137 return; 138 139 [gfx::SkColorToCalibratedNSColor(app_list::kSelectedColor) set]; 140 NSRectFillUsingOperation(dirtyRect, NSCompositeSourceOver); 141} 142 143- (void)mouseDown:(NSEvent*)theEvent { 144 [[[self button] cell] setHighlighted:YES]; 145} 146 147- (void)mouseDragged:(NSEvent*)theEvent { 148 NSPoint pointInView = [self convertPoint:[theEvent locationInWindow] 149 fromView:nil]; 150 BOOL isInView = [self mouse:pointInView inRect:[self bounds]]; 151 [[[self button] cell] setHighlighted:isInView]; 152} 153 154- (void)mouseUp:(NSEvent*)theEvent { 155 NSPoint pointInView = [self convertPoint:[theEvent locationInWindow] 156 fromView:nil]; 157 if (![self mouse:pointInView inRect:[self bounds]]) 158 return; 159 160 [[self button] performClick:self]; 161} 162 163@end 164 165@interface AppsGridViewItem () 166 167- (AppsGridItemBackgroundView*)itemBackgroundView; 168 169@end 170 171@implementation AppsGridViewItem 172 173- (id)initWithSize:(NSSize)tileSize { 174 if ((self = [super init])) { 175 base::scoped_nsobject<AppsGridItemButton> prototypeButton( 176 [[AppsGridItemButton alloc] initWithFrame:NSMakeRect( 177 0, 0, tileSize.width, tileSize.height - kTileTopPadding)]); 178 179 // This NSButton style always positions the icon at the very top of the 180 // button frame. AppsGridViewItem uses an enclosing view so that it is 181 // visually correct. 182 [prototypeButton setImagePosition:NSImageAbove]; 183 [prototypeButton setButtonType:NSMomentaryChangeButton]; 184 [prototypeButton setBordered:NO]; 185 186 base::scoped_nsobject<AppsGridItemBackgroundView> prototypeButtonBackground( 187 [[AppsGridItemBackgroundView alloc] 188 initWithFrame:NSMakeRect(0, 0, tileSize.width, tileSize.height)]); 189 [prototypeButtonBackground addSubview:prototypeButton]; 190 [self setView:prototypeButtonBackground]; 191 } 192 return self; 193} 194 195- (NSString*)buttonTitle { 196 return [[[self button] attributedTitle] string]; 197} 198 199- (void)setButtonTitle:(NSString*)newTitle { 200 base::scoped_nsobject<NSMutableParagraphStyle> paragraphStyle( 201 [[NSMutableParagraphStyle alloc] init]); 202 [paragraphStyle setLineBreakMode:NSLineBreakByTruncatingTail]; 203 [paragraphStyle setAlignment:NSCenterTextAlignment]; 204 NSDictionary* titleAttributes = @{ 205 NSParagraphStyleAttributeName : paragraphStyle, 206 NSFontAttributeName : ui::ResourceBundle::GetSharedInstance().GetFont( 207 app_list::kItemTextFontStyle).GetNativeFont(), 208 NSForegroundColorAttributeName : [self isSelected] ? 209 gfx::SkColorToCalibratedNSColor(app_list::kGridTitleHoverColor) : 210 gfx::SkColorToCalibratedNSColor(app_list::kGridTitleColor) 211 }; 212 base::scoped_nsobject<NSAttributedString> attributedTitle( 213 [[NSAttributedString alloc] initWithString:newTitle 214 attributes:titleAttributes]); 215 [[self button] setAttributedTitle:attributedTitle]; 216} 217 218- (void)setModel:(app_list::AppListItemModel*)itemModel { 219 if (!itemModel) { 220 observerBridge_.reset(); 221 return; 222 } 223 224 NSButton* button = [self button]; 225 [self setButtonTitle:base::SysUTF8ToNSString(itemModel->title())]; 226 [button setImage:gfx::NSImageFromImageSkia(itemModel->icon())]; 227 [[button cell] setHasShadow:itemModel->has_shadow()]; 228 observerBridge_.reset(new app_list::ItemModelObserverBridge(self, itemModel)); 229 230 if (trackingArea_.get()) 231 [[self view] removeTrackingArea:trackingArea_.get()]; 232 233 trackingArea_.reset( 234 [[CrTrackingArea alloc] initWithRect:NSZeroRect 235 options:NSTrackingInVisibleRect | 236 NSTrackingMouseEnteredAndExited | 237 NSTrackingActiveInKeyWindow 238 owner:self 239 userInfo:nil]); 240 [[self view] addTrackingArea:trackingArea_.get()]; 241} 242 243- (app_list::AppListItemModel*)model { 244 return observerBridge_->model(); 245} 246 247- (NSButton*)button { 248 DCHECK_EQ(1u, [[[self view] subviews] count]); 249 return base::mac::ObjCCastStrict<NSButton>( 250 [[[self view] subviews] objectAtIndex:0]); 251} 252 253- (NSMenu*)contextMenu { 254 [self setSelected:YES]; 255 return observerBridge_->GetContextMenu(); 256} 257 258- (AppsGridItemBackgroundView*)itemBackgroundView { 259 return base::mac::ObjCCastStrict<AppsGridItemBackgroundView>([self view]); 260} 261 262- (void)mouseEntered:(NSEvent*)theEvent { 263 [self setSelected:YES]; 264} 265 266- (void)mouseExited:(NSEvent*)theEvent { 267 [self setSelected:NO]; 268} 269 270- (void)setSelected:(BOOL)flag { 271 if ([self isSelected] == flag) 272 return; 273 274 [[self itemBackgroundView] setSelected:flag]; 275 [super setSelected:flag]; 276 [self setButtonTitle:[self buttonTitle]]; 277} 278 279@end 280 281@implementation AppsGridItemButton 282 283+ (Class)cellClass { 284 return [AppsGridItemButtonCell class]; 285} 286 287@end 288 289@implementation AppsGridItemButtonCell 290 291@synthesize hasShadow = hasShadow_; 292 293- (void)drawImage:(NSImage*)image 294 withFrame:(NSRect)frame 295 inView:(NSView*)controlView { 296 if (!hasShadow_) { 297 [super drawImage:image 298 withFrame:frame 299 inView:controlView]; 300 return; 301 } 302 303 base::scoped_nsobject<NSShadow> shadow([[NSShadow alloc] init]); 304 gfx::ScopedNSGraphicsContextSaveGState context; 305 [shadow setShadowOffset:NSMakeSize(0, -2)]; 306 [shadow setShadowBlurRadius:2.0]; 307 [shadow setShadowColor:[NSColor colorWithCalibratedWhite:0 308 alpha:0.14]]; 309 [shadow set]; 310 311 [super drawImage:image 312 withFrame:frame 313 inView:controlView]; 314} 315 316@end 317