mac: Redraw bookmark bar view buttons when the bookmark bar changes size.
[chromium-blink-merge.git] / chrome / browser / ui / cocoa / bookmarks / bookmark_button_cell.mm
blobc861fdcfef67bae896a6d64b3767dfd08247b0f2
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;
26 @end
29 @implementation BookmarkButtonCell
31 @synthesize startingChildIndex = startingChildIndex_;
32 @synthesize drawFolderArrow = drawFolderArrow_;
34 + (id)buttonCellForNode:(const BookmarkNode*)node
35                    text:(NSString*)text
36                   image:(NSImage*)image
37          menuController:(BookmarkContextMenuCocoaController*)menuController {
38   id buttonCell =
39       [[[BookmarkButtonCell alloc] initForNode:node
40                                           text:text
41                                          image:image
42                                 menuController:menuController]
43        autorelease];
44   return buttonCell;
47 + (id)buttonCellWithText:(NSString*)text
48                    image:(NSImage*)image
49           menuController:(BookmarkContextMenuCocoaController*)menuController {
50   id buttonCell =
51       [[[BookmarkButtonCell alloc] initWithText:text
52                                           image:image
53                                  menuController:menuController]
54        autorelease];
55   return buttonCell;
58 - (id)initForNode:(const BookmarkNode*)node
59              text:(NSString*)text
60             image:(NSImage*)image
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
70     // is hovered.
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];
77     if (node) {
78       NSString* title = base::SysUTF16ToNSString(node->GetTitle());
79       [self setBookmarkCellText:title image:image];
80     } else {
81       [self setEmpty:YES];
82       [self setBookmarkCellText:l10n_util::GetNSString(IDS_MENU_EMPTY_SUBMENU)
83                           image:nil];
84     }
85   }
87   return self;
90 - (id)initWithText:(NSString*)text
91              image:(NSImage*)image
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.
101     [self setEmpty:NO];
102   }
104   return self;
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 {
118   return NO;
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]]];
129   [self setWraps:NO];
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];
142 - (BOOL)empty {
143   return empty_;
146 - (void)setEmpty:(BOOL)empty {
147   empty_ = 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.
155   size.width += 2;
156   size.height += 4;
157   return size;
160 - (void)setBookmarkCellText:(NSString*)title
161                       image:(NSImage*)image {
162   title = [title stringByReplacingOccurrencesOfString:@"\n"
163                                            withString:@" "];
164   title = [title stringByReplacingOccurrencesOfString:@"\r"
165                                            withString:@" "];
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
172     // there is a title.
173     [self setImagePosition:NSImageLeft];
174   } else {
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
178     // empty title.
179     [self setImagePosition:NSImageOnly];
180   }
182   if (image)
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]
192                                             pointerValue]);
195 - (NSMenu*)menu {
196   // If node is NULL, this is a custom button, the menu does not represent
197   // anything.
198   const BookmarkNode* node = [self bookmarkNode];
200   if (node && node->parent() &&
201       node->parent()->type() == BookmarkNode::FOLDER) {
202     content::RecordAction(UserMetricsAction("BookmarkBarFolder_CtxMenu"));
203   } else {
204     content::RecordAction(UserMetricsAction("BookmarkBar_CtxMenu"));
205   }
206   return [menuController_ menuForBookmarkNode:node];
209 - (void)setTitle:(NSString*)title {
210   if ([[self title] isEqualTo:title])
211     return;
212   [super setTitle:title];
213   [self applyTextColor];
216 - (void)setTextColor:(NSColor*)color {
217   if ([textColor_ isEqualTo:color])
218     return;
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,
234                          nil];
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();
260     arrowImage_.reset(
261         [rb.GetNativeImageNamed(IDR_MENU_HIERARCHY_ARROW).ToNSImage() retain]);
262   }
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.
267 - (NSSize)cellSize {
268   NSSize cellSize = [super cellSize];
269   if (drawFolderArrow_) {
270     cellSize.width += [arrowImage_ size].width + 2 * kHierarchyButtonXMargin;
271   }
272   return cellSize;
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_)
282     return;
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.
289     CGFloat dX =
290         NSWidth(cellFrame) - NSWidth(imageRect) - kHierarchyButtonXMargin;
291     CGFloat dY = (NSHeight(cellFrame) / 2.0) - (NSHeight(imageRect) / 2.0) +
292         kArrowOffset;
293     NSRect drawRect = NSOffsetRect(imageRect, dX, dY);
294     [arrowImage_ drawInRect:drawRect
295                     fromRect:imageRect
296                    operation:NSCompositeSourceOver
297                     fraction:[self isEnabled] ? 1.0 : 0.5
298               respectFlipped:YES
299                        hints:nil];
300   }
303 - (int)verticalTextOffset {
304   return 0;
307 @end