1 // Copyright (c) 2013 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_textfield.h"
10 #include "base/logging.h"
11 #include "ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h"
13 const CGFloat kGap = 6.0; // gap between icon and text.
14 const CGFloat kMinimumHeight = 27.0; // Enforced minimum height for text cells.
16 @interface AutofillTextFieldCell ()
17 - (NSRect)textFrameForFrame:(NSRect)frame;
20 @interface AutofillTextField ()
21 // Resize to accommodate contents, but keep width fixed.
25 @implementation AutofillTextField
27 @synthesize inputDelegate = inputDelegate_;
28 @synthesize isMultiline = isMultiline_;
31 return [AutofillTextFieldCell class];
34 - (id)initWithFrame:(NSRect)frame {
35 if (self = [super initWithFrame:frame])
36 [super setDelegate:self];
40 - (BOOL)becomeFirstResponder {
41 BOOL result = [super becomeFirstResponder];
42 if (result && inputDelegate_) {
43 [inputDelegate_ fieldBecameFirstResponder:self];
44 shouldFilterClick_ = YES;
49 - (void)onEditorMouseDown:(id)sender {
50 // Since the dialog does not care about clicks that gave firstResponder
51 // status, swallow those.
52 if (!handlingFirstClick_)
53 [inputDelegate_ onMouseDown: self];
56 - (NSRect)decorationFrame {
57 return [[self cell] decorationFrameForFrame:[self frame]];
60 - (void)mouseDown:(NSEvent*)theEvent {
61 // mouseDown: is only invoked for a click that actually gave firstResponder
62 // status to the NSTextField, and clicks to the border area. Further clicks
63 // into the content are are handled by the field editor instead.
64 handlingFirstClick_ = shouldFilterClick_;
65 [super mouseDown:theEvent];
66 handlingFirstClick_ = NO;
67 shouldFilterClick_ = NO;
70 - (void)controlTextDidEndEditing:(NSNotification*)notification {
72 [inputDelegate_ didEndEditing:self];
75 - (void)controlTextDidChange:(NSNotification*)aNotification {
77 [inputDelegate_ didChange:self];
80 - (BOOL)control:(NSControl*)control
81 textView:(NSTextView*)textView
82 doCommandBySelector:(SEL)commandSelector {
83 // No special command handling for single line inputs.
84 if (![self isMultiline])
87 if (commandSelector == @selector(insertNewline:) ||
88 commandSelector == @selector(insertNewlineIgnoringFieldEditor:)) {
89 // Only allow newline at end of text.
90 NSRange selectionRange = [textView selectedRange];
91 if (selectionRange.location < [[textView string] length])
94 // Only allow newline on a non-empty line.
95 NSRange lineRange = [[textView string] lineRangeForRange:selectionRange];
96 if (lineRange.length == 0)
99 // Insert a line-break character without ending editing.
100 [textView insertNewlineIgnoringFieldEditor:self];
108 - (void)resizeToText {
109 NSSize size = [[self cell] cellSize];
110 size.width = NSWidth([self frame]);
111 [self setFrameSize:size];
113 id delegate = [[self window] windowController];
114 if ([delegate respondsToSelector:@selector(requestRelayout)])
115 [delegate performSelector:@selector(requestRelayout)];
118 - (NSString*)fieldValue {
119 return [[self cell] fieldValue];
122 - (void)setFieldValue:(NSString*)fieldValue {
123 [[self cell] setFieldValue:fieldValue];
124 if ([self isMultiline])
128 - (NSString*)defaultValue {
129 return [[self cell] defaultValue];
132 - (void)setDefaultValue:(NSString*)defaultValue {
133 [[self cell] setDefaultValue:defaultValue];
137 return [[[self cell] fieldValue] isEqualToString:[[self cell] defaultValue]];
140 - (NSString*)validityMessage {
141 return validityMessage_;
144 - (void)setValidityMessage:(NSString*)validityMessage {
145 validityMessage_.reset([validityMessage copy]);
146 [[self cell] setInvalid:[self invalid]];
150 return [validityMessage_ length] != 0;
156 @implementation AutofillTextFieldCell
158 @synthesize invalid = invalid_;
159 @synthesize defaultValue = defaultValue_;
160 @synthesize decorationSize = decorationSize_;
162 - (void)setInvalid:(BOOL)invalid {
164 [[self controlView] setNeedsDisplay:YES];
171 - (void)setIcon:(NSImage*)icon {
172 icon_.reset([icon retain]);
173 [self setDecorationSize:[icon_ size]];
174 [[self controlView] setNeedsDisplay:YES];
177 - (NSString*)fieldValue {
178 return [self stringValue];
181 - (void)setFieldValue:(NSString*)fieldValue {
182 [self setStringValue:fieldValue];
185 - (NSRect)textFrameForFrame:(NSRect)frame {
186 // Ensure text height is original cell height, and the text frame is centered
187 // vertically in the cell frame.
188 NSSize originalSize = [super cellSize];
189 if (originalSize.height < NSHeight(frame)) {
190 CGFloat delta = NSHeight(frame) - originalSize.height;
191 frame.origin.y += std::floor(delta / 2.0);
192 frame.size.height -= delta;
194 DCHECK_EQ(originalSize.height, NSHeight(frame));
196 if (decorationSize_.width > 0) {
197 NSRect textFrame, decorationFrame;
198 NSDivideRect(frame, &decorationFrame, &textFrame,
199 kGap + decorationSize_.width, NSMaxXEdge);
205 - (NSRect)decorationFrameForFrame:(NSRect)frame {
206 NSRect decorationFrame;
207 if (decorationSize_.width > 0) {
209 NSDivideRect(frame, &decorationFrame, &textFrame,
210 kGap + decorationSize_.width, NSMaxXEdge);
211 decorationFrame.size = decorationSize_;
212 decorationFrame.origin.y +=
213 roundf((NSHeight(frame) - NSHeight(decorationFrame)) / 2.0);
215 return decorationFrame;
219 NSSize cellSize = [super cellSize];
221 if (decorationSize_.width > 0) {
222 cellSize.width += kGap + decorationSize_.width;
223 cellSize.height = std::max(cellSize.height, decorationSize_.height);
225 cellSize.height = std::max(cellSize.height, kMinimumHeight);
229 - (void)editWithFrame:(NSRect)cellFrame
230 inView:(NSView *)controlView
231 editor:(NSText *)editor
232 delegate:(id)delegate
233 event:(NSEvent *)event {
234 [super editWithFrame:[self textFrameForFrame:cellFrame]
241 - (void)selectWithFrame:(NSRect)cellFrame
242 inView:(NSView *)controlView
243 editor:(NSText *)editor
244 delegate:(id)delegate
245 start:(NSInteger)start
246 length:(NSInteger)length {
247 [super selectWithFrame:[self textFrameForFrame:cellFrame]
255 - (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView*)controlView {
256 NSRect textFrame = [self textFrameForFrame:cellFrame];
257 [super drawInteriorWithFrame:textFrame inView:controlView];
260 NSRect iconFrame = [self decorationFrameForFrame:cellFrame];
261 [icon_ drawInRect:iconFrame
263 operation:NSCompositeSourceOver
270 - (void)drawWithFrame:(NSRect)cellFrame inView:(NSView*)controlView {
271 // If the control is disabled and doesn't have text, don't draw it.
272 if (![self isEnabled] && ([[self stringValue] length] == 0))
275 [super drawWithFrame:cellFrame inView:controlView];
278 gfx::ScopedNSGraphicsContextSaveGState state;
280 // Render red border for invalid fields.
281 [[NSColor colorWithDeviceRed:1.0 green:0.0 blue:0.0 alpha:1.0] setStroke];
282 [[NSBezierPath bezierPathWithRect:NSInsetRect(cellFrame, 0.5, 0.5)] stroke];