blob: e95bea9dbb9910071305acea2af39d3c9a7c0df9 [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/mac/mac_util.h"
#include "base/strings/sys_string_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/ui/autofill/autofill_popup_controller.h"
#include "chrome/browser/ui/autofill/autofill_popup_layout_model.h"
#include "chrome/browser/ui/cocoa/autofill/autofill_popup_view_bridge.h"
#include "components/autofill/core/browser/popup_item_ids.h"
#include "components/autofill/core/browser/suggestion.h"
#include "components/toolbar/vector_icons.h"
#include "skia/ext/skia_utils_mac.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/base/cocoa/window_size_constants.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/gfx/color_palette.h"
#include "ui/gfx/font_list.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/image/image.h"
#include "ui/gfx/image/image_skia_util_mac.h"
#include "ui/gfx/paint_vector_icon.h"
#include "ui/native_theme/native_theme.h"
#include "ui/native_theme/native_theme_mac.h"
using autofill::AutofillPopupView;
using autofill::AutofillPopupLayoutModel;
@interface AutofillPopupViewCocoa ()
#pragma mark -
#pragma mark Private methods
// 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
textYOffset:(CGFloat)textYOffset;
// This comment block applies to all three draw* methods that follow.
// If |rightAlign| == YES.
// Draws the widget with right border aligned to |x|.
// Returns the x value of left border of the widget.
// If |rightAlign| == NO.
// Draws the widget with left border aligned to |x|.
// Returns the x value of right border of the widget.
- (CGFloat)drawName:(NSString*)name
atX:(CGFloat)x
index:(size_t)index
rightAlign:(BOOL)rightAlign
bounds:(NSRect)bounds
textYOffset:(CGFloat)textYOffset;
- (CGFloat)drawIconAtIndex:(size_t)index
atX:(CGFloat)x
rightAlign:(BOOL)rightAlign
bounds:(NSRect)bounds;
- (CGFloat)drawSubtext:(NSString*)subtext
atX:(CGFloat)x
index:(size_t)index
rightAlign:(BOOL)rightAlign
bounds:(NSRect)bounds
textYOffset:(CGFloat)textYOffset;
// 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 delegate:NULL];
}
- (id)initWithController:(autofill::AutofillPopupController*)controller
frame:(NSRect)frame
delegate:(autofill::AutofillPopupViewCocoaDelegate*)delegate {
self = [super initWithDelegate:controller frame:frame];
if (self) {
controller_ = controller;
delegate_ = delegate;
}
return self;
}
#pragma mark -
#pragma mark NSView implementation:
- (void)drawRect:(NSRect)dirtyRect {
// If the view is in the process of being destroyed, don't bother drawing.
if (!controller_)
return;
[self drawBackgroundAndBorder];
for (size_t i = 0; i < controller_->GetLineCount(); ++i) {
// Skip rows outside of the dirty rect.
NSRect rowBounds = NSRectFromCGRect(delegate_->GetRowBounds(i).ToCGRect());
if (!NSIntersectsRect(rowBounds, dirtyRect))
continue;
const autofill::Suggestion& suggestion = controller_->GetSuggestionAt(i);
if (suggestion.frontend_id == autofill::POPUP_ITEM_ID_SEPARATOR) {
[self drawSeparatorWithBounds:rowBounds];
continue;
}
// Additional offset applied to the text in the vertical direction.
CGFloat textYOffset = 0;
NSString* value = SysUTF16ToNSString(controller_->GetElidedValueAt(i));
NSString* label = SysUTF16ToNSString(controller_->GetElidedLabelAt(i));
BOOL isSelected = static_cast<int>(i) == controller_->selected_line();
[self drawSuggestionWithName:value
subtext:label
index:i
bounds:rowBounds
selected:isSelected
textYOffset:textYOffset];
}
}
#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;
[super delegateDestroyed];
}
- (void)invalidateRow:(size_t)row {
NSRect dirty_rect = NSRectFromCGRect(delegate_->GetRowBounds(row).ToCGRect());
[self setNeedsDisplayInRect:dirty_rect];
}
#pragma mark -
#pragma mark Private API:
- (void)drawSuggestionWithName:(NSString*)name
subtext:(NSString*)subtext
index:(size_t)index
bounds:(NSRect)bounds
selected:(BOOL)isSelected
textYOffset:(CGFloat)textYOffset {
BOOL isHTTPWarning =
(controller_->GetSuggestionAt(index).frontend_id ==
autofill::POPUP_ITEM_ID_HTTP_NOT_SECURE_WARNING_MESSAGE);
// If this row is selected, highlight it with this mac system color.
// Otherwise the controller may have a specific background color for this
// entry.
if (isSelected) {
[[self highlightColor] set];
[NSBezierPath fillRect:bounds];
} else {
SkColor backgroundColor =
ui::NativeTheme::GetInstanceForNativeUi()->GetSystemColor(
controller_->GetBackgroundColorIDForRow(index));
[skia::SkColorToSRGBNSColor(backgroundColor) set];
[NSBezierPath fillRect:bounds];
}
BOOL isRTL = controller_->IsRTL();
// The X values of the left and right borders of the autofill widget.
CGFloat leftX = NSMinX(bounds) + AutofillPopupLayoutModel::kEndPadding;
CGFloat rightX = NSMaxX(bounds) - AutofillPopupLayoutModel::kEndPadding;
// Draw left side if isRTL == NO, right side if isRTL == YES.
CGFloat x = isRTL ? rightX : leftX;
if (isHTTPWarning) {
x = [self drawIconAtIndex:index atX:x rightAlign:isRTL bounds:bounds];
}
[self drawName:name
atX:x
index:index
rightAlign:isRTL
bounds:bounds
textYOffset:textYOffset];
// Draw right side if isRTL == NO, left side if isRTL == YES.
x = isRTL ? leftX : rightX;
if (!isHTTPWarning) {
x = [self drawIconAtIndex:index atX:x rightAlign:!isRTL bounds:bounds];
}
[self drawSubtext:subtext
atX:x
index:index
rightAlign:!isRTL
bounds:bounds
textYOffset:textYOffset];
}
- (CGFloat)drawName:(NSString*)name
atX:(CGFloat)x
index:(size_t)index
rightAlign:(BOOL)rightAlign
bounds:(NSRect)bounds
textYOffset:(CGFloat)textYOffset {
NSColor* nameColor = skia::SkColorToSRGBNSColor(
ui::NativeTheme::GetInstanceForNativeUi()->GetSystemColor(
controller_->layout_model().GetValueFontColorIDForRow(index)));
NSDictionary* nameAttributes = [NSDictionary
dictionaryWithObjectsAndKeys:controller_->layout_model()
.GetValueFontListForRow(index)
.GetPrimaryFont()
.GetNativeFont(),
NSFontAttributeName, nameColor,
NSForegroundColorAttributeName, nil];
NSSize nameSize = [name sizeWithAttributes:nameAttributes];
x -= rightAlign ? nameSize.width : 0;
CGFloat y = bounds.origin.y + (bounds.size.height - nameSize.height) / 2;
y += textYOffset;
[name drawAtPoint:NSMakePoint(x, y) withAttributes:nameAttributes];
x += rightAlign ? 0 : nameSize.width;
return x;
}
- (CGFloat)drawIconAtIndex:(size_t)index
atX:(CGFloat)x
rightAlign:(BOOL)rightAlign
bounds:(NSRect)bounds {
NSImage* icon = [self iconAtIndex:index];
if (!icon)
return x;
NSSize iconSize = [icon size];
x -= rightAlign ? iconSize.width : 0;
CGFloat 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 += rightAlign ? -AutofillPopupLayoutModel::kIconPadding
: iconSize.width + AutofillPopupLayoutModel::kIconPadding;
return x;
}
- (CGFloat)drawSubtext:(NSString*)subtext
atX:(CGFloat)x
index:(size_t)index
rightAlign:(BOOL)rightAlign
bounds:(NSRect)bounds
textYOffset:(CGFloat)textYOffset {
NSDictionary* subtextAttributes = [NSDictionary
dictionaryWithObjectsAndKeys:controller_->layout_model()
.GetLabelFontListForRow(index)
.GetPrimaryFont()
.GetNativeFont(),
NSFontAttributeName, [self subtextColor],
NSForegroundColorAttributeName, nil];
NSSize subtextSize = [subtext sizeWithAttributes:subtextAttributes];
x -= rightAlign ? subtextSize.width : 0;
CGFloat y = bounds.origin.y + (bounds.size.height - subtextSize.height) / 2;
y += textYOffset;
[subtext drawAtPoint:NSMakePoint(x, y) withAttributes:subtextAttributes];
x += rightAlign ? 0 : subtextSize.width;
return x;
}
- (NSImage*)iconAtIndex:(size_t)index {
const int kHttpWarningIconWidth = 16;
const base::string16& icon = controller_->GetSuggestionAt(index).icon;
if (icon.empty())
return nil;
// For the Form-Not-Secure warning about password/credit card fields on HTTP
// pages, reuse the omnibox vector icons.
if (icon == base::ASCIIToUTF16("httpWarning")) {
return NSImageFromImageSkiaWithColorSpace(
gfx::CreateVectorIcon(toolbar::kHttpIcon, kHttpWarningIconWidth,
gfx::kChromeIconGrey),
base::mac::GetSRGBColorSpace());
}
if (icon == base::ASCIIToUTF16("httpsInvalid")) {
return NSImageFromImageSkiaWithColorSpace(
gfx::CreateVectorIcon(toolbar::kHttpsInvalidIcon, kHttpWarningIconWidth,
gfx::kGoogleRed700),
base::mac::GetSRGBColorSpace());
}
int iconId = delegate_->GetIconResourceID(icon);
DCHECK_NE(-1, iconId);
ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
return rb.GetNativeImageNamed(iconId).ToNSImage();
}
@end