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.
5 #import "chrome/browser/ui/cocoa/bookmarks/bookmark_button_cell.h"
7 #include "base/logging.h"
8 #include "base/strings/sys_string_conversions.h"
9 #import "chrome/browser/ui/cocoa/bookmarks/bookmark_button.h"
10 #import "chrome/browser/ui/cocoa/bookmarks/bookmark_context_menu_cocoa_controller.h"
11 #include "chrome/grit/generated_resources.h"
12 #import "components/bookmarks/browser/bookmark_model.h"
13 #include "content/public/browser/user_metrics.h"
14 #include "ui/base/l10n/l10n_util_mac.h"
15 #include "ui/base/resource/resource_bundle.h"
16 #include "ui/resources/grit/ui_resources.h"
18 using base::UserMetricsAction;
19 using bookmarks::BookmarkNode;
21 const int kHierarchyButtonXMargin = 4;
23 @interface BookmarkButtonCell(Private)
24 - (void)configureBookmarkButtonCell;
25 - (void)applyTextColor;
29 @implementation BookmarkButtonCell
31 @synthesize startingChildIndex = startingChildIndex_;
32 @synthesize drawFolderArrow = drawFolderArrow_;
34 + (id)buttonCellForNode:(const BookmarkNode*)node
37 menuController:(BookmarkContextMenuCocoaController*)menuController {
39 [[[BookmarkButtonCell alloc] initForNode:node
42 menuController:menuController]
47 + (id)buttonCellWithText:(NSString*)text
49 menuController:(BookmarkContextMenuCocoaController*)menuController {
51 [[[BookmarkButtonCell alloc] initWithText:text
53 menuController:menuController]
58 - (id)initForNode:(const BookmarkNode*)node
61 menuController:(BookmarkContextMenuCocoaController*)menuController {
62 if ((self = [super initTextCell:text])) {
63 menuController_ = menuController;
64 [self configureBookmarkButtonCell];
65 [self setTextColor:[NSColor blackColor]];
66 [self setBookmarkNode:node];
67 // When opening a bookmark folder, the default behavior is that the
68 // favicon is greyed when menu item is hovered with the mouse cursor.
69 // When using NSNoCellMask, the favicon won't be greyed when menu item
71 // In the bookmark bar, the favicon is not greyed when the bookmark is
72 // hovered with the mouse cursor.
73 // It makes the behavior of the bookmark folder consistent with hovering
74 // on the bookmark bar.
75 [self setHighlightsBy:NSNoCellMask];
78 NSString* title = base::SysUTF16ToNSString(node->GetTitle());
79 [self setBookmarkCellText:title image:image];
82 [self setBookmarkCellText:l10n_util::GetNSString(IDS_MENU_EMPTY_SUBMENU)
90 - (id)initWithText:(NSString*)text
92 menuController:(BookmarkContextMenuCocoaController*)menuController {
93 if ((self = [super initTextCell:text])) {
94 menuController_ = menuController;
95 [self configureBookmarkButtonCell];
96 [self setTextColor:[NSColor blackColor]];
97 [self setBookmarkNode:NULL];
98 [self setBookmarkCellText:text image:image];
99 // This is a custom button not attached to any node. It is no considered
100 // empty even if its bookmark node is NULL.
107 - (id)initTextCell:(NSString*)string {
108 return [self initForNode:nil text:string image:nil menuController:nil];
111 // Used by the off-the-side menu, the only case where a
112 // BookmarkButtonCell is loaded from a nib.
113 - (void)awakeFromNib {
114 [self configureBookmarkButtonCell];
117 - (BOOL)isFolderButtonCell {
121 // Perform all normal init routines specific to the BookmarkButtonCell.
122 - (void)configureBookmarkButtonCell {
123 [self setButtonType:NSMomentaryPushInButton];
124 [self setBezelStyle:NSShadowlessSquareBezelStyle];
125 [self setShowsBorderOnlyWhileMouseInside:YES];
126 [self setControlSize:NSSmallControlSize];
127 [self setAlignment:NSLeftTextAlignment];
128 [self setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]];
130 // NSLineBreakByTruncatingMiddle seems more common on OSX but let's
131 // try to match Windows for a bit to see what happens.
132 [self setLineBreakMode:NSLineBreakByTruncatingTail];
134 // The overflow button chevron bitmap is not 16 units high, so it'd be scaled
135 // at paint time without this.
136 [self setImageScaling:NSImageScaleNone];
138 // Theming doesn't work for bookmark buttons yet (cell text is chucked).
139 [super setShouldTheme:NO];
146 - (void)setEmpty:(BOOL)empty {
148 [self setShowsBorderOnlyWhileMouseInside:!empty];
151 - (NSSize)cellSizeForBounds:(NSRect)aRect {
152 NSSize size = [super cellSizeForBounds:aRect];
153 // Cocoa seems to slightly underestimate how much space we need, so we
154 // compensate here to avoid a clipped rendering.
160 - (void)setBookmarkCellText:(NSString*)title
161 image:(NSImage*)image {
162 title = [title stringByReplacingOccurrencesOfString:@"\n"
164 title = [title stringByReplacingOccurrencesOfString:@"\r"
167 if ([title length]) {
168 [self setImagePosition:NSImageLeft];
169 [self setTitle:title];
170 } else if ([self isFolderButtonCell]) {
171 // Left-align icons for bookmarks within folders, regardless of whether
173 [self setImagePosition:NSImageLeft];
175 // For bookmarks without a title that aren't visible directly in the
176 // bookmarks bar, squeeze things tighter by displaying only the image.
177 // By default, Cocoa leaves extra space in an attempt to display an
179 [self setImagePosition:NSImageOnly];
183 [self setImage:image];
186 - (void)setBookmarkNode:(const BookmarkNode*)node {
187 [self setRepresentedObject:[NSValue valueWithPointer:node]];
190 - (const BookmarkNode*)bookmarkNode {
191 return static_cast<const BookmarkNode*>([[self representedObject]
196 // If node is NULL, this is a custom button, the menu does not represent
198 const BookmarkNode* node = [self bookmarkNode];
200 if (node && node->parent() &&
201 node->parent()->type() == BookmarkNode::FOLDER) {
202 content::RecordAction(UserMetricsAction("BookmarkBarFolder_CtxMenu"));
204 content::RecordAction(UserMetricsAction("BookmarkBar_CtxMenu"));
206 return [menuController_ menuForBookmarkNode:node];
209 - (void)setTitle:(NSString*)title {
210 if ([[self title] isEqualTo:title])
212 [super setTitle:title];
213 [self applyTextColor];
216 - (void)setTextColor:(NSColor*)color {
217 if ([textColor_ isEqualTo:color])
219 textColor_.reset([color copy]);
220 [self applyTextColor];
223 // We must reapply the text color after any setTitle: call
224 - (void)applyTextColor {
225 base::scoped_nsobject<NSMutableParagraphStyle> style(
226 [NSMutableParagraphStyle new]);
227 [style setAlignment:NSLeftTextAlignment];
228 NSDictionary* dict = [NSDictionary
229 dictionaryWithObjectsAndKeys:textColor_,
230 NSForegroundColorAttributeName,
231 [self font], NSFontAttributeName,
232 style.get(), NSParagraphStyleAttributeName,
233 [NSNumber numberWithFloat:0.2], NSKernAttributeName,
235 base::scoped_nsobject<NSAttributedString> ats(
236 [[NSAttributedString alloc] initWithString:[self title] attributes:dict]);
237 [self setAttributedTitle:ats.get()];
240 // To implement "hover open a bookmark button to open the folder"
241 // which feels like menus, we override NSButtonCell's mouseEntered:
242 // and mouseExited:, then and pass them along to our owning control.
243 // Note: as verified in a debugger, mouseEntered: does NOT increase
244 // the retainCount of the cell or its owning control.
245 - (void)mouseEntered:(NSEvent*)event {
246 [super mouseEntered:event];
247 [[self controlView] mouseEntered:event];
250 // See comment above mouseEntered:, above.
251 - (void)mouseExited:(NSEvent*)event {
252 [[self controlView] mouseExited:event];
253 [super mouseExited:event];
256 - (void)setDrawFolderArrow:(BOOL)draw {
257 drawFolderArrow_ = draw;
258 if (draw && !arrowImage_) {
259 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
261 [rb.GetNativeImageNamed(IDR_MENU_HIERARCHY_ARROW).ToNSImage() retain]);
265 // Add extra size for the arrow so it doesn't overlap the text.
266 // Does not sanity check to be sure this is actually a folder node.
268 NSSize cellSize = [super cellSize];
269 if (drawFolderArrow_) {
270 cellSize.width += [arrowImage_ size].width + 2 * kHierarchyButtonXMargin;
275 // Override cell drawing to add a submenu arrow like a real menu.
276 - (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView*)controlView {
277 // First draw "everything else".
278 [super drawInteriorWithFrame:cellFrame inView:controlView];
280 // If asked to do so, and if a folder, draw the arrow.
281 if (!drawFolderArrow_)
283 BookmarkButton* button = static_cast<BookmarkButton*>([self controlView]);
284 DCHECK([button respondsToSelector:@selector(isFolder)]);
285 if ([button isFolder]) {
286 NSRect imageRect = NSZeroRect;
287 imageRect.size = [arrowImage_ size];
288 const CGFloat kArrowOffset = 1.0; // Required for proper centering.
290 NSWidth(cellFrame) - NSWidth(imageRect) - kHierarchyButtonXMargin;
291 CGFloat dY = (NSHeight(cellFrame) / 2.0) - (NSHeight(imageRect) / 2.0) +
293 NSRect drawRect = NSOffsetRect(imageRect, dX, dY);
294 [arrowImage_ drawInRect:drawRect
296 operation:NSCompositeSourceOver
297 fraction:[self isEnabled] ? 1.0 : 0.5
303 - (int)verticalTextOffset {