blob: 7989d864dbbc6ae708986a8c7ea4656a321a5926 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "chrome/browser/ui/cocoa/autofill/autofill_popup_view_cocoa.h"
#include "base/logging.h"
#include "base/strings/sys_string_conversions.h"
#include "chrome/browser/ui/autofill/autofill_popup_controller.h"
#include "chrome/browser/ui/cocoa/autofill/autofill_popup_view_bridge.h"
#include "grit/ui_resources.h"
#include "third_party/WebKit/public/web/WebAutofillClient.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/gfx/image/image.h"
#include "ui/gfx/point.h"
#include "ui/gfx/rect.h"
using autofill::AutofillPopupView;
namespace {
NSColor* BackgroundColor() {
return [NSColor whiteColor];
}
// The color of the border around the popup.
NSColor* BorderColor() {
return [NSColor colorForControlTint:[NSColor currentControlTint]];
}
NSColor* SeparatorColor() {
return [NSColor colorWithCalibratedWhite:220 / 255.0 alpha:1];
}
NSColor* HighlightColor() {
return [NSColor selectedControlColor];
}
NSColor* NameColor() {
return [NSColor blackColor];
}
NSColor* WarningColor() {
return [NSColor grayColor];
}
NSColor* SubtextColor() {
return [NSColor grayColor];
}
} // namespace
#pragma mark -
#pragma mark Private methods
@interface AutofillPopupViewCocoa ()
// Draws a thin separator in the popup UI.
- (void)drawSeparatorWithBounds:(NSRect)bounds;
// Draws an Autofill suggestion in the given |bounds|, labeled with the given
// |name| and |subtext| hint. If the suggestion |isSelected|, then it is drawn
// with a highlight. |index| determines the font to use, as well as the icon,
// if the row requires it -- such as for credit cards.
- (void)drawSuggestionWithName:(NSString*)name
subtext:(NSString*)subtext
index:(size_t)index
bounds:(NSRect)bounds
selected:(BOOL)isSelected;
// Returns the icon for the row with the given |index|, or |nil| if there is
// none.
- (NSImage*)iconAtIndex:(size_t)index;
@end
@implementation AutofillPopupViewCocoa
#pragma mark -
#pragma mark Initialisers
- (id)initWithFrame:(NSRect)frame {
NOTREACHED();
return [self initWithController:NULL frame:frame];
}
- (id)initWithController:(autofill::AutofillPopupController*)controller
frame:(NSRect)frame {
self = [super initWithFrame:frame];
if (self)
controller_ = controller;
return self;
}
#pragma mark -
#pragma mark NSView implementation:
// A slight optimization for drawing:
// https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/CocoaViewsGuide/Optimizing/Optimizing.html
- (BOOL)isOpaque {
return YES;
}
- (BOOL)isFlipped {
// Flipped so that it's easier to share controller logic with other OSes.
return YES;
}
- (void)drawRect:(NSRect)dirtyRect {
// If the view is in the process of being destroyed, don't bother drawing.
if (!controller_)
return;
// Draw the popup's background and border.
// The inset is needed since the border is centered on the |path|.
// TODO(isherman): We should consider using asset-based drawing for the
// border, creating simple bitmaps for the view's border and background, and
// drawing them using NSDrawNinePartImage().
CGFloat inset = autofill::AutofillPopupView::kBorderThickness / 2.0;
NSRect borderRect = NSInsetRect([self bounds], inset, inset);
NSBezierPath* path = [NSBezierPath bezierPathWithRect:borderRect];
[BackgroundColor() setFill];
[path fill];
[path setLineWidth:autofill::AutofillPopupView::kBorderThickness];
[BorderColor() setStroke];
[path stroke];
for (size_t i = 0; i < controller_->names().size(); ++i) {
// Skip rows outside of the dirty rect.
NSRect rowBounds =
NSRectFromCGRect(controller_->GetRowBounds(i).ToCGRect());
if (!NSIntersectsRect(rowBounds, dirtyRect))
continue;
if (controller_->identifiers()[i] ==
WebKit::WebAutofillClient::MenuItemIDSeparator) {
[self drawSeparatorWithBounds:rowBounds];
} else {
NSString* name = SysUTF16ToNSString(controller_->names()[i]);
NSString* subtext = SysUTF16ToNSString(controller_->subtexts()[i]);
BOOL isSelected = static_cast<int>(i) == controller_->selected_line();
[self drawSuggestionWithName:name
subtext:subtext
index:i
bounds:rowBounds
selected:isSelected];
}
}
}
- (void)mouseUp:(NSEvent*)theEvent {
// If the view is in the process of being destroyed, abort.
if (!controller_)
return;
NSPoint location = [self convertPoint:[theEvent locationInWindow]
fromView:nil];
if (NSPointInRect(location, [self bounds])) {
controller_->MouseClicked(static_cast<int>(location.x),
static_cast<int>(location.y));
}
}
- (void)mouseMoved:(NSEvent*)theEvent {
// If the view is in the process of being destroyed, abort.
if (!controller_)
return;
NSPoint location = [self convertPoint:[theEvent locationInWindow]
fromView:nil];
controller_->MouseHovered(static_cast<int>(location.x),
static_cast<int>(location.y));
}
- (void)mouseDragged:(NSEvent*)theEvent {
[self mouseMoved:theEvent];
}
- (void)mouseExited:(NSEvent*)theEvent {
// If the view is in the process of being destroyed, abort.
if (!controller_)
return;
controller_->MouseExitedPopup();
}
#pragma mark -
#pragma mark Public API:
- (void)controllerDestroyed {
// Since the |controller_| either already has been destroyed or is about to
// be, about the only thing we can safely do with it is to null it out.
controller_ = NULL;
}
#pragma mark -
#pragma mark Private API:
- (void)drawSeparatorWithBounds:(NSRect)bounds {
[SeparatorColor() set];
[NSBezierPath fillRect:bounds];
}
- (void)drawSuggestionWithName:(NSString*)name
subtext:(NSString*)subtext
index:(size_t)index
bounds:(NSRect)bounds
selected:(BOOL)isSelected {
// If this row is selected, highlight it.
if (isSelected) {
[HighlightColor() set];
[NSBezierPath fillRect:bounds];
}
BOOL isRTL = controller_->IsRTL();
NSColor* nameColor =
controller_->IsWarning(index) ? WarningColor() : NameColor();
NSDictionary* nameAttributes =
[NSDictionary dictionaryWithObjectsAndKeys:
controller_->GetNameFontForRow(index).GetNativeFont(),
NSFontAttributeName, nameColor, NSForegroundColorAttributeName,
nil];
NSSize nameSize = [name sizeWithAttributes:nameAttributes];
CGFloat x = bounds.origin.x +
(isRTL ?
bounds.size.width - AutofillPopupView::kEndPadding - nameSize.width :
AutofillPopupView::kEndPadding);
CGFloat y = bounds.origin.y + (bounds.size.height - nameSize.height) / 2;
[name drawAtPoint:NSMakePoint(x, y) withAttributes:nameAttributes];
// The x-coordinate will be updated as each element is drawn.
x = bounds.origin.x +
(isRTL ?
AutofillPopupView::kEndPadding :
bounds.size.width - AutofillPopupView::kEndPadding);
// Draw the Autofill icon, if one exists.
NSImage* icon = [self iconAtIndex:index];
if (icon) {
NSSize iconSize = [icon size];
x += isRTL ? 0 : -iconSize.width;
y = bounds.origin.y + (bounds.size.height - iconSize.height) / 2;
[icon drawInRect:NSMakeRect(x, y, iconSize.width, iconSize.height)
fromRect:NSZeroRect
operation:NSCompositeSourceOver
fraction:1.0
respectFlipped:YES
hints:nil];
x += isRTL ?
iconSize.width + AutofillPopupView::kIconPadding :
-AutofillPopupView::kIconPadding;
}
// Draw the subtext.
NSDictionary* subtextAttributes =
[NSDictionary dictionaryWithObjectsAndKeys:
controller_->subtext_font().GetNativeFont(), NSFontAttributeName,
SubtextColor(), NSForegroundColorAttributeName,
nil];
NSSize subtextSize = [subtext sizeWithAttributes:subtextAttributes];
x += isRTL ? 0 : -subtextSize.width;
y = bounds.origin.y + (bounds.size.height - subtextSize.height) / 2;
[subtext drawAtPoint:NSMakePoint(x, y) withAttributes:subtextAttributes];
}
- (NSImage*)iconAtIndex:(size_t)index {
if (controller_->icons()[index].empty())
return nil;
int iconId = controller_->GetIconResourceID(controller_->icons()[index]);
DCHECK_NE(-1, iconId);
ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
return rb.GetNativeImageNamed(iconId).ToNSImage();
}
@end