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. 4 5 #import "chrome/browser/ui/cocoa/autofill/autofill_popup_view_cocoa.h" 6 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/image/image.h" 15 #include "ui/gfx/point.h" 16 #include "ui/gfx/rect.h" 17 18 using autofill::AutofillPopupView; 19 20 namespace { 21 22 NSColor* BackgroundColor() { 23 return [NSColor whiteColor]; 24 } 25 26 NSColor* SeparatorColor() { 27 return [NSColor colorWithCalibratedWhite:220 / 255.0 alpha:1]; 28 } 29 30 NSColor* HighlightColor() { 31 return [NSColor selectedControlColor]; 32 } 33 34 NSColor* NameColor() { 35 return [NSColor blackColor]; 36 } 37 38 NSColor* WarningColor() { 39 return [NSColor grayColor]; 40 } 41 42 NSColor* SubtextColor() { 43 return [NSColor grayColor]; 44 } 45 46 } // namespace 47 48 #pragma mark - 49 #pragma mark Private methods 50 51 @interface AutofillPopupViewCocoa () 52 53 // Draws a thin separator in the popup UI. 54 - (void)drawSeparatorWithBounds:(NSRect)bounds; 55 56 // Draws an Autofill suggestion in the given |bounds|, labeled with the given 57 // |name| and |subtext| hint. If the suggestion |isSelected|, then it is drawn 58 // with a highlight. |index| determines the font to use, as well as the icon, 59 // if the row requires it -- such as for credit cards. 60 - (void)drawSuggestionWithName:(NSString*)name 61 subtext:(NSString*)subtext 62 index:(size_t)index 63 bounds:(NSRect)bounds 64 selected:(BOOL)isSelected; 65 66 // Returns the icon for the row with the given |index|, or |nil| if there is 67 // none. 68 - (NSImage*)iconAtIndex:(size_t)index; 69 70 @end 71 72 @implementation AutofillPopupViewCocoa 73 74 #pragma mark - 75 #pragma mark Initialisers 76 77 - (id)initWithFrame:(NSRect)frame { 78 NOTREACHED(); 79 return [self initWithController:NULL frame:frame]; 80 } 81 82 - (id)initWithController:(autofill::AutofillPopupController*)controller 83 frame:(NSRect)frame { 84 self = [super initWithFrame:frame]; 85 if (self) 86 controller_ = controller; 87 88 return self; 89 } 90 91 #pragma mark - 92 #pragma mark NSView implementation: 93 94 // A slight optimization for drawing: 95 // https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/CocoaViewsGuide/Optimizing/Optimizing.html 96 - (BOOL)isOpaque { 97 return YES; 98 } 99 100 - (BOOL)isFlipped { 101 // Flipped so that it's easier to share controller logic with other OSes. 102 return YES; 103 } 104 105 - (void)drawRect:(NSRect)dirtyRect { 106 // If the view is in the process of being destroyed, don't bother drawing. 107 if (!controller_) 108 return; 109 110 [BackgroundColor() set]; 111 [NSBezierPath fillRect:[self bounds]]; 112 113 for (size_t i = 0; i < controller_->names().size(); ++i) { 114 // Skip rows outside of the dirty rect. 115 NSRect rowBounds = 116 NSRectFromCGRect(controller_->GetRowBounds(i).ToCGRect()); 117 if (!NSIntersectsRect(rowBounds, dirtyRect)) 118 continue; 119 120 if (controller_->identifiers()[i] == 121 WebKit::WebAutofillClient::MenuItemIDSeparator) { 122 [self drawSeparatorWithBounds:rowBounds]; 123 } else { 124 NSString* name = SysUTF16ToNSString(controller_->names()[i]); 125 NSString* subtext = SysUTF16ToNSString(controller_->subtexts()[i]); 126 BOOL isSelected = static_cast<int>(i) == controller_->selected_line(); 127 [self drawSuggestionWithName:name 128 subtext:subtext 129 index:i 130 bounds:rowBounds 131 selected:isSelected]; 132 } 133 } 134 } 135 136 - (void)mouseUp:(NSEvent*)theEvent { 137 // If the view is in the process of being destroyed, abort. 138 if (!controller_) 139 return; 140 141 NSPoint location = [self convertPoint:[theEvent locationInWindow] 142 fromView:nil]; 143 144 if (NSPointInRect(location, [self bounds])) { 145 controller_->MouseClicked(static_cast<int>(location.x), 146 static_cast<int>(location.y)); 147 } 148 } 149 150 - (void)mouseMoved:(NSEvent*)theEvent { 151 // If the view is in the process of being destroyed, abort. 152 if (!controller_) 153 return; 154 155 NSPoint location = [self convertPoint:[theEvent locationInWindow] 156 fromView:nil]; 157 158 controller_->MouseHovered(static_cast<int>(location.x), 159 static_cast<int>(location.y)); 160 } 161 162 - (void)mouseDragged:(NSEvent*)theEvent { 163 [self mouseMoved:theEvent]; 164 } 165 166 - (void)mouseExited:(NSEvent*)theEvent { 167 // If the view is in the process of being destroyed, abort. 168 if (!controller_) 169 return; 170 171 controller_->MouseExitedPopup(); 172 } 173 174 #pragma mark - 175 #pragma mark Public API: 176 177 - (void)controllerDestroyed { 178 // Since the |controller_| either already has been destroyed or is about to 179 // be, about the only thing we can safely do with it is to null it out. 180 controller_ = NULL; 181 } 182 183 #pragma mark - 184 #pragma mark Private API: 185 186 - (void)drawSeparatorWithBounds:(NSRect)bounds { 187 [SeparatorColor() set]; 188 [NSBezierPath fillRect:bounds]; 189 } 190 191 - (void)drawSuggestionWithName:(NSString*)name 192 subtext:(NSString*)subtext 193 index:(size_t)index 194 bounds:(NSRect)bounds 195 selected:(BOOL)isSelected { 196 // If this row is selected, highlight it. 197 if (isSelected) { 198 [HighlightColor() set]; 199 [NSBezierPath fillRect:bounds]; 200 } 201 202 BOOL isRTL = controller_->IsRTL(); 203 204 NSColor* nameColor = 205 controller_->IsWarning(index) ? WarningColor() : NameColor(); 206 NSDictionary* nameAttributes = 207 [NSDictionary dictionaryWithObjectsAndKeys: 208 controller_->GetNameFontForRow(index).GetNativeFont(), 209 NSFontAttributeName, nameColor, NSForegroundColorAttributeName, 210 nil]; 211 NSSize nameSize = [name sizeWithAttributes:nameAttributes]; 212 CGFloat x = bounds.origin.x + 213 (isRTL ? 214 bounds.size.width - AutofillPopupView::kEndPadding - nameSize.width : 215 AutofillPopupView::kEndPadding); 216 CGFloat y = bounds.origin.y + (bounds.size.height - nameSize.height) / 2; 217 218 [name drawAtPoint:NSMakePoint(x, y) withAttributes:nameAttributes]; 219 220 // The x-coordinate will be updated as each element is drawn. 221 x = bounds.origin.x + 222 (isRTL ? 223 AutofillPopupView::kEndPadding : 224 bounds.size.width - AutofillPopupView::kEndPadding); 225 226 // Draw the Autofill icon, if one exists. 227 NSImage* icon = [self iconAtIndex:index]; 228 if (icon) { 229 NSSize iconSize = [icon size]; 230 x += isRTL ? 0 : -iconSize.width; 231 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]; 238 239 x += isRTL ? 240 iconSize.width + AutofillPopupView::kIconPadding : 241 -AutofillPopupView::kIconPadding; 242 } 243 244 // Draw the subtext. 245 NSDictionary* subtextAttributes = 246 [NSDictionary dictionaryWithObjectsAndKeys: 247 controller_->subtext_font().GetNativeFont(), NSFontAttributeName, 248 SubtextColor(), NSForegroundColorAttributeName, 249 nil]; 250 NSSize subtextSize = [subtext sizeWithAttributes:subtextAttributes]; 251 x += isRTL ? 0 : -subtextSize.width; 252 y = bounds.origin.y + (bounds.size.height - subtextSize.height) / 2; 253 254 [subtext drawAtPoint:NSMakePoint(x, y) withAttributes:subtextAttributes]; 255 } 256 257 - (NSImage*)iconAtIndex:(size_t)index { 258 if (controller_->icons()[index].empty()) 259 return nil; 260 261 int iconId = controller_->GetIconResourceID(controller_->icons()[index]); 262 DCHECK_NE(-1, iconId); 263 264 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); 265 return rb.GetNativeImageNamed(iconId).ToNSImage(); 266 } 267 268 @end 269