Add ICU message format support
[chromium-blink-merge.git] / chrome / browser / ui / cocoa / autofill / autofill_popup_view_cocoa.mm
blob3cedd133f64ee389648e688800349a2ac707dd25
1 // Copyright (c) 2012 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/autofill/autofill_popup_view_cocoa.h"
7 #include "base/logging.h"
8 #include "base/strings/sys_string_conversions.h"
9 #include "chrome/browser/ui/autofill/autofill_popup_controller.h"
10 #include "chrome/browser/ui/autofill/popup_constants.h"
11 #include "chrome/browser/ui/cocoa/autofill/autofill_popup_view_bridge.h"
12 #include "components/autofill/core/browser/popup_item_ids.h"
13 #include "components/autofill/core/browser/suggestion.h"
14 #include "ui/base/cocoa/window_size_constants.h"
15 #include "ui/base/resource/resource_bundle.h"
16 #include "ui/gfx/font_list.h"
17 #include "ui/gfx/geometry/point.h"
18 #include "ui/gfx/geometry/rect.h"
19 #include "ui/gfx/image/image.h"
21 using autofill::AutofillPopupView;
23 @interface AutofillPopupViewCocoa ()
25 #pragma mark -
26 #pragma mark Private methods
28 // Draws an Autofill suggestion in the given |bounds|, labeled with the given
29 // |name| and |subtext| hint.  If the suggestion |isSelected|, then it is drawn
30 // with a highlight.  |index| determines the font to use, as well as the icon,
31 // if the row requires it -- such as for credit cards. |imageFirst| indicates
32 // whether the image should be drawn before the name, and with the same
33 // alignment, or whether it should be drawn afterwards, with the opposite
34 // alignment.
35 - (void)drawSuggestionWithName:(NSString*)name
36                        subtext:(NSString*)subtext
37                          index:(size_t)index
38                         bounds:(NSRect)bounds
39                       selected:(BOOL)isSelected
40                     imageFirst:(BOOL)imageFirst
41                    textYOffset:(CGFloat)textYOffset;
43 // This comment block applies to all three draw* methods that follow.
44 // If |rightAlign| == YES.
45 //   Draws the widget with right border aligned to |x|.
46 //   Returns the x value of left border of the widget.
47 // If |rightAlign| == NO.
48 //   Draws the widget with left border aligned to |x|.
49 //   Returns the x value of right border of the widget.
50 - (CGFloat)drawName:(NSString*)name
51                 atX:(CGFloat)x
52               index:(size_t)index
53          rightAlign:(BOOL)rightAlign
54              bounds:(NSRect)bounds
55         textYOffset:(CGFloat)textYOffset;
56 - (CGFloat)drawIconAtIndex:(size_t)index
57                        atX:(CGFloat)x
58                 rightAlign:(BOOL)rightAlign
59                     bounds:(NSRect)bounds;
60 - (CGFloat)drawSubtext:(NSString*)subtext
61                    atX:(CGFloat)x
62             rightAlign:(BOOL)rightAlign
63                 bounds:(NSRect)bounds
64            textYOffset:(CGFloat)textYOffset;
66 // Returns the icon for the row with the given |index|, or |nil| if there is
67 // none.
68 - (NSImage*)iconAtIndex:(size_t)index;
70 @end
72 @implementation AutofillPopupViewCocoa
74 #pragma mark -
75 #pragma mark Initialisers
77 - (id)initWithFrame:(NSRect)frame {
78   NOTREACHED();
79   return [self initWithController:NULL frame:frame];
82 - (id)initWithController:(autofill::AutofillPopupController*)controller
83                    frame:(NSRect)frame {
84   self = [super initWithDelegate:controller frame:frame];
85   if (self)
86     controller_ = controller;
88   return self;
91 #pragma mark -
92 #pragma mark NSView implementation:
94 - (void)drawRect:(NSRect)dirtyRect {
95   // If the view is in the process of being destroyed, don't bother drawing.
96   if (!controller_)
97     return;
99   [self drawBackgroundAndBorder];
101   for (size_t i = 0; i < controller_->GetLineCount(); ++i) {
102     // Skip rows outside of the dirty rect.
103     NSRect rowBounds =
104         NSRectFromCGRect(controller_->GetRowBounds(i).ToCGRect());
105     if (!NSIntersectsRect(rowBounds, dirtyRect))
106       continue;
107     const autofill::Suggestion& suggestion = controller_->GetSuggestionAt(i);
109     if (suggestion.frontend_id == autofill::POPUP_ITEM_ID_SEPARATOR) {
110       [self drawSeparatorWithBounds:rowBounds];
111       continue;
112     }
114     // Additional offset applied to the text in the vertical direction.
115     CGFloat textYOffset = 0;
116     BOOL imageFirst = NO;
117     if (suggestion.frontend_id == autofill::POPUP_ITEM_ID_MAC_ACCESS_CONTACTS) {
118       // Due to the weighting of the asset used for this autofill entry, the
119       // text needs to be bumped up by 1 pt to make it look vertically aligned.
120       textYOffset = -1;
121       imageFirst = YES;
122     }
124     NSString* value = SysUTF16ToNSString(controller_->GetElidedValueAt(i));
125     NSString* label = SysUTF16ToNSString(controller_->GetElidedLabelAt(i));
126     BOOL isSelected = static_cast<int>(i) == controller_->selected_line();
127     [self drawSuggestionWithName:value
128                          subtext:label
129                            index:i
130                           bounds:rowBounds
131                         selected:isSelected
132                       imageFirst:imageFirst
133                      textYOffset:textYOffset];
134   }
137 #pragma mark -
138 #pragma mark Public API:
140 - (void)controllerDestroyed {
141   // Since the |controller_| either already has been destroyed or is about to
142   // be, about the only thing we can safely do with it is to null it out.
143   controller_ = NULL;
144   [super delegateDestroyed];
147 - (void)invalidateRow:(size_t)row {
148   NSRect dirty_rect =
149       NSRectFromCGRect(controller_->GetRowBounds(row).ToCGRect());
150   [self setNeedsDisplayInRect:dirty_rect];
153 #pragma mark -
154 #pragma mark Private API:
156 - (void)drawSuggestionWithName:(NSString*)name
157                        subtext:(NSString*)subtext
158                          index:(size_t)index
159                         bounds:(NSRect)bounds
160                       selected:(BOOL)isSelected
161                     imageFirst:(BOOL)imageFirst
162                    textYOffset:(CGFloat)textYOffset {
163   // If this row is selected, highlight it.
164   if (isSelected) {
165     [[self highlightColor] set];
166     [NSBezierPath fillRect:bounds];
167   }
169   BOOL isRTL = controller_->IsRTL();
171   // The X values of the left and right borders of the autofill widget.
172   CGFloat leftX = NSMinX(bounds) + AutofillPopupView::kEndPadding;
173   CGFloat rightX = NSMaxX(bounds) - AutofillPopupView::kEndPadding;
175   // Draw left side if isRTL == NO, right side if isRTL == YES.
176   CGFloat x = isRTL ? rightX : leftX;
177   if (imageFirst)
178     x = [self drawIconAtIndex:index atX:x rightAlign:isRTL bounds:bounds];
179   [self drawName:name
180               atX:x
181             index:index
182        rightAlign:isRTL
183            bounds:bounds
184       textYOffset:textYOffset];
186   // Draw right side if isRTL == NO, left side if isRTL == YES.
187   x = isRTL ? leftX : rightX;
188   if (!imageFirst)
189     x = [self drawIconAtIndex:index atX:x rightAlign:!isRTL bounds:bounds];
190   [self drawSubtext:subtext
191                 atX:x
192          rightAlign:!isRTL
193              bounds:bounds
194         textYOffset:textYOffset];
197 - (CGFloat)drawName:(NSString*)name
198                 atX:(CGFloat)x
199               index:(size_t)index
200          rightAlign:(BOOL)rightAlign
201              bounds:(NSRect)bounds
202         textYOffset:(CGFloat)textYOffset {
203   NSColor* nameColor =
204       controller_->IsWarning(index) ? [self warningColor] : [self nameColor];
205   NSDictionary* nameAttributes =
206       [NSDictionary dictionaryWithObjectsAndKeys:
207            controller_->GetValueFontListForRow(index).GetPrimaryFont().
208                GetNativeFont(),
209            NSFontAttributeName, nameColor, NSForegroundColorAttributeName,
210            nil];
211   NSSize nameSize = [name sizeWithAttributes:nameAttributes];
212   x -= rightAlign ? nameSize.width : 0;
213   CGFloat y = bounds.origin.y + (bounds.size.height - nameSize.height) / 2;
214   y += textYOffset;
216   [name drawAtPoint:NSMakePoint(x, y) withAttributes:nameAttributes];
218   x += rightAlign ? 0 : nameSize.width;
219   return x;
222 - (CGFloat)drawIconAtIndex:(size_t)index
223                        atX:(CGFloat)x
224                 rightAlign:(BOOL)rightAlign
225                     bounds:(NSRect)bounds {
226   NSImage* icon = [self iconAtIndex:index];
227   if (!icon)
228     return x;
229   NSSize iconSize = [icon size];
230   x -= rightAlign ? iconSize.width : 0;
231   CGFloat y = bounds.origin.y + (bounds.size.height - iconSize.height) / 2;
232     [icon drawInRect:NSMakeRect(x, y, iconSize.width, iconSize.height)
233             fromRect:NSZeroRect
234            operation:NSCompositeSourceOver
235             fraction:1.0
236       respectFlipped:YES
237                hints:nil];
239     x += rightAlign ? -AutofillPopupView::kIconPadding
240                     : iconSize.width + AutofillPopupView::kIconPadding;
241     return x;
244 - (CGFloat)drawSubtext:(NSString*)subtext
245                    atX:(CGFloat)x
246             rightAlign:(BOOL)rightAlign
247                 bounds:(NSRect)bounds
248            textYOffset:(CGFloat)textYOffset {
249   NSDictionary* subtextAttributes =
250       [NSDictionary dictionaryWithObjectsAndKeys:
251            controller_->GetLabelFontList().GetPrimaryFont().GetNativeFont(),
252            NSFontAttributeName,
253            [self subtextColor],
254            NSForegroundColorAttributeName,
255            nil];
256   NSSize subtextSize = [subtext sizeWithAttributes:subtextAttributes];
257   x -= rightAlign ? subtextSize.width : 0;
258   CGFloat y = bounds.origin.y + (bounds.size.height - subtextSize.height) / 2;
259   y += textYOffset;
261   [subtext drawAtPoint:NSMakePoint(x, y) withAttributes:subtextAttributes];
262   x += rightAlign ? 0 : subtextSize.width;
263   return x;
266 - (NSImage*)iconAtIndex:(size_t)index {
267   const base::string16& icon = controller_->GetSuggestionAt(index).icon;
268   if (icon.empty())
269     return nil;
271   int iconId = controller_->GetIconResourceID(icon);
272   DCHECK_NE(-1, iconId);
274   ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
275   return rb.GetNativeImageNamed(iconId).ToNSImage();
278 @end