Home | History | Annotate | Download | only in autofill
      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