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/cocoa/autofill/autofill_popup_view_bridge.h"
11 #include "grit/ui_resources.h"
12 #include "third_party/WebKit/public/web/WebAutofillClient.h"
13 #include "ui/base/resource/resource_bundle.h"
14 #include "ui/gfx/font_list.h"
15 #include "ui/gfx/image/image.h"
16 #include "ui/gfx/point.h"
17 #include "ui/gfx/rect.h"
19 using autofill::AutofillPopupView;
23 NSColor* BackgroundColor() {
24 return [NSColor whiteColor];
27 // The color of the border around the popup.
28 NSColor* BorderColor() {
29 return [NSColor colorForControlTint:[NSColor currentControlTint]];
32 NSColor* SeparatorColor() {
33 return [NSColor colorWithCalibratedWhite:220 / 255.0 alpha:1];
36 NSColor* HighlightColor() {
37 return [NSColor selectedControlColor];
40 NSColor* NameColor() {
41 return [NSColor blackColor];
44 NSColor* WarningColor() {
45 return [NSColor grayColor];
48 NSColor* SubtextColor() {
49 return [NSColor grayColor];
55 #pragma mark Private methods
57 @interface AutofillPopupViewCocoa ()
59 // Draws a thin separator in the popup UI.
60 - (void)drawSeparatorWithBounds:(NSRect)bounds;
62 // Draws an Autofill suggestion in the given |bounds|, labeled with the given
63 // |name| and |subtext| hint. If the suggestion |isSelected|, then it is drawn
64 // with a highlight. |index| determines the font to use, as well as the icon,
65 // if the row requires it -- such as for credit cards.
66 - (void)drawSuggestionWithName:(NSString*)name
67 subtext:(NSString*)subtext
70 selected:(BOOL)isSelected;
72 // Returns the icon for the row with the given |index|, or |nil| if there is
74 - (NSImage*)iconAtIndex:(size_t)index;
78 @implementation AutofillPopupViewCocoa
81 #pragma mark Initialisers
83 - (id)initWithFrame:(NSRect)frame {
85 return [self initWithController:NULL frame:frame];
88 - (id)initWithController:(autofill::AutofillPopupController*)controller
90 self = [super initWithFrame:frame];
92 controller_ = controller;
98 #pragma mark NSView implementation:
100 // A slight optimization for drawing:
101 // https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/CocoaViewsGuide/Optimizing/Optimizing.html
107 // Flipped so that it's easier to share controller logic with other OSes.
111 - (void)drawRect:(NSRect)dirtyRect {
112 // If the view is in the process of being destroyed, don't bother drawing.
116 // Draw the popup's background and border.
117 // The inset is needed since the border is centered on the |path|.
118 // TODO(isherman): We should consider using asset-based drawing for the
119 // border, creating simple bitmaps for the view's border and background, and
120 // drawing them using NSDrawNinePartImage().
121 CGFloat inset = autofill::AutofillPopupView::kBorderThickness / 2.0;
122 NSRect borderRect = NSInsetRect([self bounds], inset, inset);
123 NSBezierPath* path = [NSBezierPath bezierPathWithRect:borderRect];
124 [BackgroundColor() setFill];
126 [path setLineWidth:autofill::AutofillPopupView::kBorderThickness];
127 [BorderColor() setStroke];
130 for (size_t i = 0; i < controller_->names().size(); ++i) {
131 // Skip rows outside of the dirty rect.
133 NSRectFromCGRect(controller_->GetRowBounds(i).ToCGRect());
134 if (!NSIntersectsRect(rowBounds, dirtyRect))
137 if (controller_->identifiers()[i] ==
138 blink::WebAutofillClient::MenuItemIDSeparator) {
139 [self drawSeparatorWithBounds:rowBounds];
141 NSString* name = SysUTF16ToNSString(controller_->names()[i]);
142 NSString* subtext = SysUTF16ToNSString(controller_->subtexts()[i]);
143 BOOL isSelected = static_cast<int>(i) == controller_->selected_line();
144 [self drawSuggestionWithName:name
148 selected:isSelected];
153 - (void)mouseUp:(NSEvent*)theEvent {
154 // If the view is in the process of being destroyed, abort.
158 NSPoint location = [self convertPoint:[theEvent locationInWindow]
161 if (NSPointInRect(location, [self bounds])) {
162 controller_->LineAcceptedAtPoint(static_cast<int>(location.x),
163 static_cast<int>(location.y));
167 - (void)mouseMoved:(NSEvent*)theEvent {
168 // If the view is in the process of being destroyed, abort.
172 NSPoint location = [self convertPoint:[theEvent locationInWindow]
175 controller_->LineSelectedAtPoint(static_cast<int>(location.x),
176 static_cast<int>(location.y));
179 - (void)mouseDragged:(NSEvent*)theEvent {
180 [self mouseMoved:theEvent];
183 - (void)mouseExited:(NSEvent*)theEvent {
184 // If the view is in the process of being destroyed, abort.
188 controller_->SelectionCleared();
192 #pragma mark Public API:
194 - (void)controllerDestroyed {
195 // Since the |controller_| either already has been destroyed or is about to
196 // be, about the only thing we can safely do with it is to null it out.
201 #pragma mark Private API:
203 - (void)drawSeparatorWithBounds:(NSRect)bounds {
204 [SeparatorColor() set];
205 [NSBezierPath fillRect:bounds];
208 - (void)drawSuggestionWithName:(NSString*)name
209 subtext:(NSString*)subtext
211 bounds:(NSRect)bounds
212 selected:(BOOL)isSelected {
213 // If this row is selected, highlight it.
215 [HighlightColor() set];
216 [NSBezierPath fillRect:bounds];
219 BOOL isRTL = controller_->IsRTL();
222 controller_->IsWarning(index) ? WarningColor() : NameColor();
223 NSDictionary* nameAttributes =
224 [NSDictionary dictionaryWithObjectsAndKeys:
225 controller_->GetNameFontListForRow(index).GetPrimaryFont().
227 NSFontAttributeName, nameColor, NSForegroundColorAttributeName,
229 NSSize nameSize = [name sizeWithAttributes:nameAttributes];
230 CGFloat x = bounds.origin.x +
232 bounds.size.width - AutofillPopupView::kEndPadding - nameSize.width :
233 AutofillPopupView::kEndPadding);
234 CGFloat y = bounds.origin.y + (bounds.size.height - nameSize.height) / 2;
236 [name drawAtPoint:NSMakePoint(x, y) withAttributes:nameAttributes];
238 // The x-coordinate will be updated as each element is drawn.
239 x = bounds.origin.x +
241 AutofillPopupView::kEndPadding :
242 bounds.size.width - AutofillPopupView::kEndPadding);
244 // Draw the Autofill icon, if one exists.
245 NSImage* icon = [self iconAtIndex:index];
247 NSSize iconSize = [icon size];
248 x += isRTL ? 0 : -iconSize.width;
249 y = bounds.origin.y + (bounds.size.height - iconSize.height) / 2;
250 [icon drawInRect:NSMakeRect(x, y, iconSize.width, iconSize.height)
252 operation:NSCompositeSourceOver
258 iconSize.width + AutofillPopupView::kIconPadding :
259 -AutofillPopupView::kIconPadding;
263 NSDictionary* subtextAttributes =
264 [NSDictionary dictionaryWithObjectsAndKeys:
265 controller_->subtext_font_list().GetPrimaryFont().GetNativeFont(),
266 NSFontAttributeName, SubtextColor(), NSForegroundColorAttributeName,
268 NSSize subtextSize = [subtext sizeWithAttributes:subtextAttributes];
269 x += isRTL ? 0 : -subtextSize.width;
270 y = bounds.origin.y + (bounds.size.height - subtextSize.height) / 2;
272 [subtext drawAtPoint:NSMakePoint(x, y) withAttributes:subtextAttributes];
275 - (NSImage*)iconAtIndex:(size_t)index {
276 if (controller_->icons()[index].empty())
279 int iconId = controller_->GetIconResourceID(controller_->icons()[index]);
280 DCHECK_NE(-1, iconId);
282 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
283 return rb.GetNativeImageNamed(iconId).ToNSImage();