Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / ui / cocoa / bookmarks / bookmark_button_cell.mm
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 "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 #import "components/bookmarks/core/browser/bookmark_model.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"
17
18 using base::UserMetricsAction;
19
20 const int kHierarchyButtonXMargin = 4;
21
22 @interface BookmarkButtonCell(Private)
23 - (void)configureBookmarkButtonCell;
24 - (void)applyTextColor;
25 @end
26
27
28 @implementation BookmarkButtonCell
29
30 @synthesize startingChildIndex = startingChildIndex_;
31 @synthesize drawFolderArrow = drawFolderArrow_;
32
33 + (id)buttonCellForNode:(const BookmarkNode*)node
34                    text:(NSString*)text
35                   image:(NSImage*)image
36          menuController:(BookmarkContextMenuCocoaController*)menuController {
37   id buttonCell =
38       [[[BookmarkButtonCell alloc] initForNode:node
39                                           text:text
40                                          image:image
41                                 menuController:menuController]
42        autorelease];
43   return buttonCell;
44 }
45
46 + (id)buttonCellWithText:(NSString*)text
47                    image:(NSImage*)image
48           menuController:(BookmarkContextMenuCocoaController*)menuController {
49   id buttonCell =
50       [[[BookmarkButtonCell alloc] initWithText:text
51                                           image:image
52                                  menuController:menuController]
53        autorelease];
54   return buttonCell;
55 }
56
57 - (id)initForNode:(const BookmarkNode*)node
58              text:(NSString*)text
59             image:(NSImage*)image
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
69     // is hovered.
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];
75
76     if (node) {
77       NSString* title = base::SysUTF16ToNSString(node->GetTitle());
78       [self setBookmarkCellText:title image:image];
79     } else {
80       [self setEmpty:YES];
81       [self setBookmarkCellText:l10n_util::GetNSString(IDS_MENU_EMPTY_SUBMENU)
82                           image:nil];
83     }
84   }
85
86   return self;
87 }
88
89 - (id)initWithText:(NSString*)text
90              image:(NSImage*)image
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.
100     [self setEmpty:NO];
101   }
102
103   return self;
104 }
105
106 - (id)initTextCell:(NSString*)string {
107   return [self initForNode:nil text:string image:nil menuController:nil];
108 }
109
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];
114 }
115
116 - (BOOL)isFolderButtonCell {
117   return NO;
118 }
119
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]]];
128   [self setWraps:NO];
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];
132
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];
136
137   // Theming doesn't work for bookmark buttons yet (cell text is chucked).
138   [super setShouldTheme:NO];
139 }
140
141 - (BOOL)empty {
142   return empty_;
143 }
144
145 - (void)setEmpty:(BOOL)empty {
146   empty_ = empty;
147   [self setShowsBorderOnlyWhileMouseInside:!empty];
148 }
149
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.
154   size.width += 2;
155   size.height += 4;
156   return size;
157 }
158
159 - (void)setBookmarkCellText:(NSString*)title
160                       image:(NSImage*)image {
161   title = [title stringByReplacingOccurrencesOfString:@"\n"
162                                            withString:@" "];
163   title = [title stringByReplacingOccurrencesOfString:@"\r"
164                                            withString:@" "];
165
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
171     // there is a title.
172     [self setImagePosition:NSImageLeft];
173   } else {
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
177     // empty title.
178     [self setImagePosition:NSImageOnly];
179   }
180
181   if (image)
182     [self setImage:image];
183 }
184
185 - (void)setBookmarkNode:(const BookmarkNode*)node {
186   [self setRepresentedObject:[NSValue valueWithPointer:node]];
187 }
188
189 - (const BookmarkNode*)bookmarkNode {
190   return static_cast<const BookmarkNode*>([[self representedObject]
191                                             pointerValue]);
192 }
193
194 - (NSMenu*)menu {
195   // If node is NULL, this is a custom button, the menu does not represent
196   // anything.
197   const BookmarkNode* node = [self bookmarkNode];
198
199   if (node && node->parent() &&
200       node->parent()->type() == BookmarkNode::FOLDER) {
201     content::RecordAction(UserMetricsAction("BookmarkBarFolder_CtxMenu"));
202   } else {
203     content::RecordAction(UserMetricsAction("BookmarkBar_CtxMenu"));
204   }
205   return [menuController_ menuForBookmarkNode:node];
206 }
207
208 - (void)setTitle:(NSString*)title {
209   if ([[self title] isEqualTo:title])
210     return;
211   [super setTitle:title];
212   [self applyTextColor];
213 }
214
215 - (void)setTextColor:(NSColor*)color {
216   if ([textColor_ isEqualTo:color])
217     return;
218   textColor_.reset([color copy]);
219   [self applyTextColor];
220 }
221
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,
233                          nil];
234   base::scoped_nsobject<NSAttributedString> ats(
235       [[NSAttributedString alloc] initWithString:[self title] attributes:dict]);
236   [self setAttributedTitle:ats.get()];
237 }
238
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];
247 }
248
249 // See comment above mouseEntered:, above.
250 - (void)mouseExited:(NSEvent*)event {
251   [[self controlView] mouseExited:event];
252   [super mouseExited:event];
253 }
254
255 - (void)setDrawFolderArrow:(BOOL)draw {
256   drawFolderArrow_ = draw;
257   if (draw && !arrowImage_) {
258     ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
259     arrowImage_.reset(
260         [rb.GetNativeImageNamed(IDR_MENU_HIERARCHY_ARROW).ToNSImage() retain]);
261   }
262 }
263
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.
266 - (NSSize)cellSize {
267   NSSize cellSize = [super cellSize];
268   if (drawFolderArrow_) {
269     cellSize.width += [arrowImage_ size].width + 2 * kHierarchyButtonXMargin;
270   }
271   return cellSize;
272 }
273
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];
278
279   // If asked to do so, and if a folder, draw the arrow.
280   if (!drawFolderArrow_)
281     return;
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.
288     CGFloat dX =
289         NSWidth(cellFrame) - NSWidth(imageRect) - kHierarchyButtonXMargin;
290     CGFloat dY = (NSHeight(cellFrame) / 2.0) - (NSHeight(imageRect) / 2.0) +
291         kArrowOffset;
292     NSRect drawRect = NSOffsetRect(imageRect, dX, dY);
293     [arrowImage_ drawInRect:drawRect
294                     fromRect:imageRect
295                    operation:NSCompositeSourceOver
296                     fraction:[self isEnabled] ? 1.0 : 0.5
297               respectFlipped:YES
298                        hints:nil];
299   }
300 }
301
302 - (int)verticalTextOffset {
303   return 0;
304 }
305
306 @end