Home | History | Annotate | Download | only in bookmarks
      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