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/bookmarks/bookmark_model.h"
10 #import "chrome/browser/ui/cocoa/bookmarks/bookmark_button.h"
11 #import "chrome/browser/ui/cocoa/bookmarks/bookmark_context_menu_cocoa_controller.h"
12 #include "content/public/browser/user_metrics.h"
13 #include "grit/generated_resources.h"
14 #include "grit/ui_resources.h"
15 #include "ui/base/l10n/l10n_util_mac.h"
16 #include "ui/base/resource/resource_bundle.h"
18 using base::UserMetricsAction;
20 const int kHierarchyButtonXMargin = 4;
22 @interface BookmarkButtonCell(Private)
23 - (void)configureBookmarkButtonCell;
24 - (void)applyTextColor;
28 @implementation BookmarkButtonCell
30 @synthesize startingChildIndex = startingChildIndex_;
31 @synthesize drawFolderArrow = drawFolderArrow_;
33 + (id)buttonCellForNode:(const BookmarkNode*)node
36 menuController:(BookmarkContextMenuCocoaController*)menuController {
38 [[[BookmarkButtonCell alloc] initForNode:node
41 menuController:menuController]
46 + (id)buttonCellWithText:(NSString*)text
48 menuController:(BookmarkContextMenuCocoaController*)menuController {
50 [[[BookmarkButtonCell alloc] initWithText:text
52 menuController:menuController]
57 - (id)initForNode:(const BookmarkNode*)node
60 menuController:(BookmarkContextMenuCocoaController*)menuController {
61 if ((self = [super initTextCell:text])) {
62 menuController_ = menuController;
63 [self configureBookmarkButtonCell];
64 [self setTextColor:[NSColor blackColor]];
65 [self setBookmarkNode:node];
66 // When opening a bookmark folder, the default behavior is that the
67 // favicon is greyed when menu item is hovered with the mouse cursor.
68 // When using NSNoCellMask, the favicon won't be greyed when menu item
70 // In the bookmark bar, the favicon is not greyed when the bookmark is
71 // hovered with the mouse cursor.
72 // It makes the behavior of the bookmark folder consistent with hovering
73 // on the bookmark bar.
74 [self setHighlightsBy:NSNoCellMask];
77 NSString* title = base::SysUTF16ToNSString(node->GetTitle());
78 [self setBookmarkCellText:title image:image];
81 [self setBookmarkCellText:l10n_util::GetNSString(IDS_MENU_EMPTY_SUBMENU)
89 - (id)initWithText:(NSString*)text
91 menuController:(BookmarkContextMenuCocoaController*)menuController {
92 if ((self = [super initTextCell:text])) {
93 menuController_ = menuController;
94 [self configureBookmarkButtonCell];
95 [self setTextColor:[NSColor blackColor]];
96 [self setBookmarkNode:NULL];
97 [self setBookmarkCellText:text image:image];
98 // This is a custom button not attached to any node. It is no considered
99 // empty even if its bookmark node is NULL.
106 - (id)initTextCell:(NSString*)string {
107 return [self initForNode:nil text:string image:nil menuController:nil];
110 // Used by the off-the-side menu, the only case where a
111 // BookmarkButtonCell is loaded from a nib.
112 - (void)awakeFromNib {
113 [self configureBookmarkButtonCell];
116 - (BOOL)isFolderButtonCell {
120 // Perform all normal init routines specific to the BookmarkButtonCell.
121 - (void)configureBookmarkButtonCell {
122 [self setButtonType:NSMomentaryPushInButton];
123 [self setBezelStyle:NSShadowlessSquareBezelStyle];
124 [self setShowsBorderOnlyWhileMouseInside:YES];
125 [self setControlSize:NSSmallControlSize];
126 [self setAlignment:NSLeftTextAlignment];
127 [self setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]];
129 // NSLineBreakByTruncatingMiddle seems more common on OSX but let's
130 // try to match Windows for a bit to see what happens.
131 [self setLineBreakMode:NSLineBreakByTruncatingTail];
133 // The overflow button chevron bitmap is not 16 units high, so it'd be scaled
134 // at paint time without this.
135 [self setImageScaling:NSImageScaleNone];
137 // Theming doesn't work for bookmark buttons yet (cell text is chucked).
138 [super setShouldTheme:NO];
145 - (void)setEmpty:(BOOL)empty {
147 [self setShowsBorderOnlyWhileMouseInside:!empty];
150 - (NSSize)cellSizeForBounds:(NSRect)aRect {
151 NSSize size = [super cellSizeForBounds:aRect];
152 // Cocoa seems to slightly underestimate how much space we need, so we
153 // compensate here to avoid a clipped rendering.
159 - (void)setBookmarkCellText:(NSString*)title
160 image:(NSImage*)image {
161 title = [title stringByReplacingOccurrencesOfString:@"\n"
163 title = [title stringByReplacingOccurrencesOfString:@"\r"
166 if ([title length]) {
167 [self setImagePosition:NSImageLeft];
168 [self setTitle:title];
169 } else if ([self isFolderButtonCell]) {
170 // Left-align icons for bookmarks within folders, regardless of whether
172 [self setImagePosition:NSImageLeft];
174 // For bookmarks without a title that aren't visible directly in the
175 // bookmarks bar, squeeze things tighter by displaying only the image.
176 // By default, Cocoa leaves extra space in an attempt to display an
178 [self setImagePosition:NSImageOnly];
182 [self setImage:image];
185 - (void)setBookmarkNode:(const BookmarkNode*)node {
186 [self setRepresentedObject:[NSValue valueWithPointer:node]];
189 - (const BookmarkNode*)bookmarkNode {
190 return static_cast<const BookmarkNode*>([[self representedObject]
195 // If node is NULL, this is a custom button, the menu does not represent
197 const BookmarkNode* node = [self bookmarkNode];
199 if (node && node->parent() &&
200 node->parent()->type() == BookmarkNode::FOLDER) {
201 content::RecordAction(UserMetricsAction("BookmarkBarFolder_CtxMenu"));
203 content::RecordAction(UserMetricsAction("BookmarkBar_CtxMenu"));
205 return [menuController_ menuForBookmarkNode:node];
208 - (void)setTitle:(NSString*)title {
209 if ([[self title] isEqualTo:title])
211 [super setTitle:title];
212 [self applyTextColor];
215 - (void)setTextColor:(NSColor*)color {
216 if ([textColor_ isEqualTo:color])
218 textColor_.reset([color copy]);
219 [self applyTextColor];
222 // We must reapply the text color after any setTitle: call
223 - (void)applyTextColor {
224 base::scoped_nsobject<NSMutableParagraphStyle> style(
225 [NSMutableParagraphStyle new]);
226 [style setAlignment:NSLeftTextAlignment];
227 NSDictionary* dict = [NSDictionary
228 dictionaryWithObjectsAndKeys:textColor_,
229 NSForegroundColorAttributeName,
230 [self font], NSFontAttributeName,
231 style.get(), NSParagraphStyleAttributeName,
232 [NSNumber numberWithFloat:0.2], NSKernAttributeName,
234 base::scoped_nsobject<NSAttributedString> ats(
235 [[NSAttributedString alloc] initWithString:[self title] attributes:dict]);
236 [self setAttributedTitle:ats.get()];
239 // To implement "hover open a bookmark button to open the folder"
240 // which feels like menus, we override NSButtonCell's mouseEntered:
241 // and mouseExited:, then and pass them along to our owning control.
242 // Note: as verified in a debugger, mouseEntered: does NOT increase
243 // the retainCount of the cell or its owning control.
244 - (void)mouseEntered:(NSEvent*)event {
245 [super mouseEntered:event];
246 [[self controlView] mouseEntered:event];
249 // See comment above mouseEntered:, above.
250 - (void)mouseExited:(NSEvent*)event {
251 [[self controlView] mouseExited:event];
252 [super mouseExited:event];
255 - (void)setDrawFolderArrow:(BOOL)draw {
256 drawFolderArrow_ = draw;
257 if (draw && !arrowImage_) {
258 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
260 [rb.GetNativeImageNamed(IDR_MENU_HIERARCHY_ARROW).ToNSImage() retain]);
264 // Add extra size for the arrow so it doesn't overlap the text.
265 // Does not sanity check to be sure this is actually a folder node.
267 NSSize cellSize = [super cellSize];
268 if (drawFolderArrow_) {
269 cellSize.width += [arrowImage_ size].width + 2 * kHierarchyButtonXMargin;
274 // Override cell drawing to add a submenu arrow like a real menu.
275 - (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView*)controlView {
276 // First draw "everything else".
277 [super drawInteriorWithFrame:cellFrame inView:controlView];
279 // If asked to do so, and if a folder, draw the arrow.
280 if (!drawFolderArrow_)
282 BookmarkButton* button = static_cast<BookmarkButton*>([self controlView]);
283 DCHECK([button respondsToSelector:@selector(isFolder)]);
284 if ([button isFolder]) {
285 NSRect imageRect = NSZeroRect;
286 imageRect.size = [arrowImage_ size];
287 const CGFloat kArrowOffset = 1.0; // Required for proper centering.
289 NSWidth(cellFrame) - NSWidth(imageRect) - kHierarchyButtonXMargin;
290 CGFloat dY = (NSHeight(cellFrame) / 2.0) - (NSHeight(imageRect) / 2.0) +
292 NSRect drawRect = NSOffsetRect(imageRect, dX, dY);
293 [arrowImage_ drawInRect:drawRect
295 operation:NSCompositeSourceOver
296 fraction:[self isEnabled] ? 1.0 : 0.5
302 - (int)verticalTextOffset {