1// Copyright (c) 2011 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 "chrome/browser/ui/cocoa/bookmarks/bookmark_button_cell.h" 6 7#include "app/mac/nsimage_cache.h" 8#include "base/logging.h" 9#include "base/sys_string_conversions.h" 10#import "chrome/browser/bookmarks/bookmark_model.h" 11#include "chrome/browser/metrics/user_metrics.h" 12#import "chrome/browser/ui/cocoa/bookmarks/bookmark_menu.h" 13#import "chrome/browser/ui/cocoa/bookmarks/bookmark_button.h" 14#import "chrome/browser/ui/cocoa/image_utils.h" 15#include "grit/generated_resources.h" 16#include "ui/base/l10n/l10n_util_mac.h" 17 18 19@interface BookmarkButtonCell(Private) 20- (void)configureBookmarkButtonCell; 21@end 22 23 24@implementation BookmarkButtonCell 25 26@synthesize startingChildIndex = startingChildIndex_; 27@synthesize drawFolderArrow = drawFolderArrow_; 28 29+ (id)buttonCellForNode:(const BookmarkNode*)node 30 contextMenu:(NSMenu*)contextMenu 31 cellText:(NSString*)cellText 32 cellImage:(NSImage*)cellImage { 33 id buttonCell = 34 [[[BookmarkButtonCell alloc] initForNode:node 35 contextMenu:contextMenu 36 cellText:cellText 37 cellImage:cellImage] 38 autorelease]; 39 return buttonCell; 40} 41 42- (id)initForNode:(const BookmarkNode*)node 43 contextMenu:(NSMenu*)contextMenu 44 cellText:(NSString*)cellText 45 cellImage:(NSImage*)cellImage { 46 if ((self = [super initTextCell:cellText])) { 47 [self configureBookmarkButtonCell]; 48 49 [self setBookmarkNode:node]; 50 51 if (node) { 52 NSString* title = base::SysUTF16ToNSString(node->GetTitle()); 53 [self setBookmarkCellText:title image:cellImage]; 54 [self setMenu:contextMenu]; 55 } else { 56 [self setEmpty:YES]; 57 [self setBookmarkCellText:l10n_util::GetNSString(IDS_MENU_EMPTY_SUBMENU) 58 image:nil]; 59 } 60 } 61 62 return self; 63} 64 65- (id)initTextCell:(NSString*)string { 66 return [self initForNode:nil contextMenu:nil cellText:string cellImage:nil]; 67} 68 69// Used by the off-the-side menu, the only case where a 70// BookmarkButtonCell is loaded from a nib. 71- (void)awakeFromNib { 72 [self configureBookmarkButtonCell]; 73} 74 75- (BOOL)isFolderButtonCell { 76 return NO; 77} 78 79// Perform all normal init routines specific to the BookmarkButtonCell. 80- (void)configureBookmarkButtonCell { 81 [self setButtonType:NSMomentaryPushInButton]; 82 [self setBezelStyle:NSShadowlessSquareBezelStyle]; 83 [self setShowsBorderOnlyWhileMouseInside:YES]; 84 [self setControlSize:NSSmallControlSize]; 85 [self setAlignment:NSLeftTextAlignment]; 86 [self setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]]; 87 [self setWraps:NO]; 88 // NSLineBreakByTruncatingMiddle seems more common on OSX but let's 89 // try to match Windows for a bit to see what happens. 90 [self setLineBreakMode:NSLineBreakByTruncatingTail]; 91 92 // Theming doesn't work for bookmark buttons yet (cell text is chucked). 93 [super setShouldTheme:NO]; 94} 95 96- (BOOL)empty { 97 return empty_; 98} 99 100- (void)setEmpty:(BOOL)empty { 101 empty_ = empty; 102 [self setShowsBorderOnlyWhileMouseInside:!empty]; 103} 104 105- (NSSize)cellSizeForBounds:(NSRect)aRect { 106 NSSize size = [super cellSizeForBounds:aRect]; 107 // Cocoa seems to slightly underestimate how much space we need, so we 108 // compensate here to avoid a clipped rendering. 109 size.width += 2; 110 size.height += 4; 111 return size; 112} 113 114- (void)setBookmarkCellText:(NSString*)title 115 image:(NSImage*)image { 116 title = [title stringByReplacingOccurrencesOfString:@"\n" 117 withString:@" "]; 118 title = [title stringByReplacingOccurrencesOfString:@"\r" 119 withString:@" "]; 120 // If there is no title, squeeze things tight by displaying only the image; by 121 // default, Cocoa leaves extra space in an attempt to display an empty title. 122 if ([title length]) { 123 [self setImagePosition:NSImageLeft]; 124 [self setTitle:title]; 125 } else { 126 [self setImagePosition:NSImageOnly]; 127 } 128 129 if (image) 130 [self setImage:image]; 131} 132 133- (void)setBookmarkNode:(const BookmarkNode*)node { 134 [self setRepresentedObject:[NSValue valueWithPointer:node]]; 135} 136 137- (const BookmarkNode*)bookmarkNode { 138 return static_cast<const BookmarkNode*>([[self representedObject] 139 pointerValue]); 140} 141 142// We share the context menu among all bookmark buttons. To allow us 143// to disambiguate when needed (e.g. "open bookmark"), we set the 144// menu's associated bookmark node ID to be our represented object. 145- (NSMenu*)menu { 146 if (empty_) 147 return nil; 148 BookmarkMenu* menu = (BookmarkMenu*)[super menu]; 149 const BookmarkNode* node = 150 static_cast<const BookmarkNode*>([[self representedObject] pointerValue]); 151 152 if (node->parent() && node->parent()->type() == BookmarkNode::FOLDER) { 153 UserMetrics::RecordAction(UserMetricsAction("BookmarkBarFolder_CtxMenu")); 154 } else { 155 UserMetrics::RecordAction(UserMetricsAction("BookmarkBar_CtxMenu")); 156 } 157 158 [menu setRepresentedObject:[NSNumber numberWithLongLong:node->id()]]; 159 160 return menu; 161} 162 163// Unfortunately, NSCell doesn't already have something like this. 164// TODO(jrg): consider placing in GTM. 165- (void)setTextColor:(NSColor*)color { 166 167 // We can't properly set the cell's text color without a control. 168 // In theory we could just save the next for later and wait until 169 // the cell is moved to a control, but there is no obvious way to 170 // accomplish that (e.g. no "cellDidMoveToControl" notification.) 171 DCHECK([self controlView]); 172 173 scoped_nsobject<NSMutableParagraphStyle> style([NSMutableParagraphStyle new]); 174 [style setAlignment:NSLeftTextAlignment]; 175 NSDictionary* dict = [NSDictionary 176 dictionaryWithObjectsAndKeys:color, 177 NSForegroundColorAttributeName, 178 [self font], NSFontAttributeName, 179 style.get(), NSParagraphStyleAttributeName, 180 nil]; 181 scoped_nsobject<NSAttributedString> ats([[NSAttributedString alloc] 182 initWithString:[self title] 183 attributes:dict]); 184 NSButton* button = static_cast<NSButton*>([self controlView]); 185 if (button) { 186 DCHECK([button isKindOfClass:[NSButton class]]); 187 [button setAttributedTitle:ats.get()]; 188 } 189} 190 191// To implement "hover open a bookmark button to open the folder" 192// which feels like menus, we override NSButtonCell's mouseEntered: 193// and mouseExited:, then and pass them along to our owning control. 194// Note: as verified in a debugger, mouseEntered: does NOT increase 195// the retainCount of the cell or its owning control. 196- (void)mouseEntered:(NSEvent*)event { 197 [super mouseEntered:event]; 198 [[self controlView] mouseEntered:event]; 199} 200 201// See comment above mouseEntered:, above. 202- (void)mouseExited:(NSEvent*)event { 203 [[self controlView] mouseExited:event]; 204 [super mouseExited:event]; 205} 206 207- (void)setDrawFolderArrow:(BOOL)draw { 208 drawFolderArrow_ = draw; 209 if (draw && !arrowImage_) { 210 arrowImage_.reset( 211 [app::mac::GetCachedImageWithName(@"menu_hierarchy_arrow.pdf") retain]); 212 } 213} 214 215// Add extra size for the arrow so it doesn't overlap the text. 216// Does not sanity check to be sure this is actually a folder node. 217- (NSSize)cellSize { 218 NSSize cellSize = [super cellSize]; 219 if (drawFolderArrow_) { 220 cellSize.width += [arrowImage_ size].width; // plus margin? 221 } 222 return cellSize; 223} 224 225// Override cell drawing to add a submenu arrow like a real menu. 226- (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView*)controlView { 227 // First draw "everything else". 228 [super drawInteriorWithFrame:cellFrame inView:controlView]; 229 230 // If asked to do so, and if a folder, draw the arrow. 231 if (!drawFolderArrow_) 232 return; 233 BookmarkButton* button = static_cast<BookmarkButton*>([self controlView]); 234 DCHECK([button respondsToSelector:@selector(isFolder)]); 235 if ([button isFolder]) { 236 NSRect imageRect = NSZeroRect; 237 imageRect.size = [arrowImage_ size]; 238 const CGFloat kArrowOffset = 1.0; // Required for proper centering. 239 CGFloat dX = NSWidth(cellFrame) - NSWidth(imageRect); 240 CGFloat dY = (NSHeight(cellFrame) / 2.0) - (NSHeight(imageRect) / 2.0) + 241 kArrowOffset; 242 NSRect drawRect = NSOffsetRect(imageRect, dX, dY); 243 [arrowImage_ drawInRect:drawRect 244 fromRect:imageRect 245 operation:NSCompositeSourceOver 246 fraction:[self isEnabled] ? 1.0 : 0.5 247 neverFlipped:YES]; 248 } 249} 250 251@end 252