blob: 3b5903b8142430f05a584fb946be0283a7ea997b [file] [log] [blame]
// Copyright (c) 2011 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 "chrome/browser/ui/cocoa/bookmarks/bookmark_button_cell.h"
#include "base/logging.h"
#include "base/strings/sys_string_conversions.h"
#import "chrome/browser/bookmarks/bookmark_model.h"
#import "chrome/browser/ui/cocoa/bookmarks/bookmark_button.h"
#import "chrome/browser/ui/cocoa/bookmarks/bookmark_context_menu_cocoa_controller.h"
#include "content/public/browser/user_metrics.h"
#include "grit/generated_resources.h"
#include "grit/ui_resources.h"
#include "ui/base/l10n/l10n_util_mac.h"
#include "ui/base/resource/resource_bundle.h"
using content::UserMetricsAction;
const int kHierarchyButtonXMargin = 4;
@interface BookmarkButtonCell(Private)
- (void)configureBookmarkButtonCell;
- (void)applyTextColor;
@end
@implementation BookmarkButtonCell
@synthesize startingChildIndex = startingChildIndex_;
@synthesize drawFolderArrow = drawFolderArrow_;
+ (id)buttonCellForNode:(const BookmarkNode*)node
text:(NSString*)text
image:(NSImage*)image
menuController:(BookmarkContextMenuCocoaController*)menuController {
id buttonCell =
[[[BookmarkButtonCell alloc] initForNode:node
text:text
image:image
menuController:menuController]
autorelease];
return buttonCell;
}
+ (id)buttonCellWithText:(NSString*)text
image:(NSImage*)image
menuController:(BookmarkContextMenuCocoaController*)menuController {
id buttonCell =
[[[BookmarkButtonCell alloc] initWithText:text
image:image
menuController:menuController]
autorelease];
return buttonCell;
}
- (id)initForNode:(const BookmarkNode*)node
text:(NSString*)text
image:(NSImage*)image
menuController:(BookmarkContextMenuCocoaController*)menuController {
if ((self = [super initTextCell:text])) {
menuController_ = menuController;
[self configureBookmarkButtonCell];
[self setTextColor:[NSColor blackColor]];
[self setBookmarkNode:node];
// When opening a bookmark folder, the default behavior is that the
// favicon is greyed when menu item is hovered with the mouse cursor.
// When using NSNoCellMask, the favicon won't be greyed when menu item
// is hovered.
// In the bookmark bar, the favicon is not greyed when the bookmark is
// hovered with the mouse cursor.
// It makes the behavior of the bookmark folder consistent with hovering
// on the bookmark bar.
[self setHighlightsBy:NSNoCellMask];
if (node) {
NSString* title = base::SysUTF16ToNSString(node->GetTitle());
[self setBookmarkCellText:title image:image];
} else {
[self setEmpty:YES];
[self setBookmarkCellText:l10n_util::GetNSString(IDS_MENU_EMPTY_SUBMENU)
image:nil];
}
}
return self;
}
- (id)initWithText:(NSString*)text
image:(NSImage*)image
menuController:(BookmarkContextMenuCocoaController*)menuController {
if ((self = [super initTextCell:text])) {
menuController_ = menuController;
[self configureBookmarkButtonCell];
[self setTextColor:[NSColor blackColor]];
[self setBookmarkNode:NULL];
[self setBookmarkCellText:text image:image];
// This is a custom button not attached to any node. It is no considered
// empty even if its bookmark node is NULL.
[self setEmpty:NO];
}
return self;
}
- (id)initTextCell:(NSString*)string {
return [self initForNode:nil text:string image:nil menuController:nil];
}
// Used by the off-the-side menu, the only case where a
// BookmarkButtonCell is loaded from a nib.
- (void)awakeFromNib {
[self configureBookmarkButtonCell];
}
- (BOOL)isFolderButtonCell {
return NO;
}
// Perform all normal init routines specific to the BookmarkButtonCell.
- (void)configureBookmarkButtonCell {
[self setButtonType:NSMomentaryPushInButton];
[self setBezelStyle:NSShadowlessSquareBezelStyle];
[self setShowsBorderOnlyWhileMouseInside:YES];
[self setControlSize:NSSmallControlSize];
[self setAlignment:NSLeftTextAlignment];
[self setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]];
[self setWraps:NO];
// NSLineBreakByTruncatingMiddle seems more common on OSX but let's
// try to match Windows for a bit to see what happens.
[self setLineBreakMode:NSLineBreakByTruncatingTail];
// The overflow button chevron bitmap is not 16 units high, so it'd be scaled
// at paint time without this.
[self setImageScaling:NSImageScaleNone];
// Theming doesn't work for bookmark buttons yet (cell text is chucked).
[super setShouldTheme:NO];
}
- (BOOL)empty {
return empty_;
}
- (void)setEmpty:(BOOL)empty {
empty_ = empty;
[self setShowsBorderOnlyWhileMouseInside:!empty];
}
- (NSSize)cellSizeForBounds:(NSRect)aRect {
NSSize size = [super cellSizeForBounds:aRect];
// Cocoa seems to slightly underestimate how much space we need, so we
// compensate here to avoid a clipped rendering.
size.width += 2;
size.height += 4;
return size;
}
- (void)setBookmarkCellText:(NSString*)title
image:(NSImage*)image {
title = [title stringByReplacingOccurrencesOfString:@"\n"
withString:@" "];
title = [title stringByReplacingOccurrencesOfString:@"\r"
withString:@" "];
if ([title length]) {
[self setImagePosition:NSImageLeft];
[self setTitle:title];
} else if ([self isFolderButtonCell]) {
// Left-align icons for bookmarks within folders, regardless of whether
// there is a title.
[self setImagePosition:NSImageLeft];
} else {
// For bookmarks without a title that aren't visible directly in the
// bookmarks bar, squeeze things tighter by displaying only the image.
// By default, Cocoa leaves extra space in an attempt to display an
// empty title.
[self setImagePosition:NSImageOnly];
}
if (image)
[self setImage:image];
}
- (void)setBookmarkNode:(const BookmarkNode*)node {
[self setRepresentedObject:[NSValue valueWithPointer:node]];
}
- (const BookmarkNode*)bookmarkNode {
return static_cast<const BookmarkNode*>([[self representedObject]
pointerValue]);
}
- (NSMenu*)menu {
// If node is NULL, this is a custom button, the menu does not represent
// anything.
const BookmarkNode* node = [self bookmarkNode];
if (node && node->parent() &&
node->parent()->type() == BookmarkNode::FOLDER) {
content::RecordAction(UserMetricsAction("BookmarkBarFolder_CtxMenu"));
} else {
content::RecordAction(UserMetricsAction("BookmarkBar_CtxMenu"));
}
return [menuController_ menuForBookmarkNode:node];
}
- (void)setTitle:(NSString*)title {
if ([[self title] isEqualTo:title])
return;
[super setTitle:title];
[self applyTextColor];
}
- (void)setTextColor:(NSColor*)color {
if ([textColor_ isEqualTo:color])
return;
textColor_.reset([color copy]);
[self applyTextColor];
}
// We must reapply the text color after any setTitle: call
- (void)applyTextColor {
base::scoped_nsobject<NSMutableParagraphStyle> style(
[NSMutableParagraphStyle new]);
[style setAlignment:NSLeftTextAlignment];
NSDictionary* dict = [NSDictionary
dictionaryWithObjectsAndKeys:textColor_,
NSForegroundColorAttributeName,
[self font], NSFontAttributeName,
style.get(), NSParagraphStyleAttributeName,
[NSNumber numberWithFloat:0.2], NSKernAttributeName,
nil];
base::scoped_nsobject<NSAttributedString> ats(
[[NSAttributedString alloc] initWithString:[self title] attributes:dict]);
[self setAttributedTitle:ats.get()];
}
// To implement "hover open a bookmark button to open the folder"
// which feels like menus, we override NSButtonCell's mouseEntered:
// and mouseExited:, then and pass them along to our owning control.
// Note: as verified in a debugger, mouseEntered: does NOT increase
// the retainCount of the cell or its owning control.
- (void)mouseEntered:(NSEvent*)event {
[super mouseEntered:event];
[[self controlView] mouseEntered:event];
}
// See comment above mouseEntered:, above.
- (void)mouseExited:(NSEvent*)event {
[[self controlView] mouseExited:event];
[super mouseExited:event];
}
- (void)setDrawFolderArrow:(BOOL)draw {
drawFolderArrow_ = draw;
if (draw && !arrowImage_) {
ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
arrowImage_.reset(
[rb.GetNativeImageNamed(IDR_MENU_HIERARCHY_ARROW).ToNSImage() retain]);
}
}
// Add extra size for the arrow so it doesn't overlap the text.
// Does not sanity check to be sure this is actually a folder node.
- (NSSize)cellSize {
NSSize cellSize = [super cellSize];
if (drawFolderArrow_) {
cellSize.width += [arrowImage_ size].width + 2 * kHierarchyButtonXMargin;
}
return cellSize;
}
// Override cell drawing to add a submenu arrow like a real menu.
- (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView*)controlView {
// First draw "everything else".
[super drawInteriorWithFrame:cellFrame inView:controlView];
// If asked to do so, and if a folder, draw the arrow.
if (!drawFolderArrow_)
return;
BookmarkButton* button = static_cast<BookmarkButton*>([self controlView]);
DCHECK([button respondsToSelector:@selector(isFolder)]);
if ([button isFolder]) {
NSRect imageRect = NSZeroRect;
imageRect.size = [arrowImage_ size];
const CGFloat kArrowOffset = 1.0; // Required for proper centering.
CGFloat dX =
NSWidth(cellFrame) - NSWidth(imageRect) - kHierarchyButtonXMargin;
CGFloat dY = (NSHeight(cellFrame) / 2.0) - (NSHeight(imageRect) / 2.0) +
kArrowOffset;
NSRect drawRect = NSOffsetRect(imageRect, dX, dY);
[arrowImage_ drawInRect:drawRect
fromRect:imageRect
operation:NSCompositeSourceOver
fraction:[self isEnabled] ? 1.0 : 0.5
respectFlipped:YES
hints:nil];
}
}
- (int)verticalTextOffset {
return 0;
}
@end