Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / chrome / browser / ui / cocoa / autofill / autofill_textfield.mm
blobf9814dc871a7934baaa93a410c4db29e90fc0bdb
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"
7 #include <algorithm>
8 #include <cmath>
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;
18 @end
20 @interface AutofillTextField ()
21 // Resize to accommodate contents, but keep width fixed.
22 - (void)resizeToText;
23 @end
25 @implementation AutofillTextField
27 @synthesize inputDelegate = inputDelegate_;
28 @synthesize isMultiline = isMultiline_;
30 + (Class)cellClass {
31   return [AutofillTextFieldCell class];
34 - (id)initWithFrame:(NSRect)frame {
35   if (self = [super initWithFrame:frame])
36     [super setDelegate:self];
37   return self;
40 - (BOOL)becomeFirstResponder {
41   BOOL result = [super becomeFirstResponder];
42   if (result && inputDelegate_) {
43     [inputDelegate_ fieldBecameFirstResponder:self];
44     shouldFilterClick_ = YES;
45   }
46   return result;
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 {
71   if (inputDelegate_)
72     [inputDelegate_ didEndEditing:self];
75 - (void)controlTextDidChange:(NSNotification*)aNotification {
76   if (inputDelegate_)
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])
85     return NO;
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])
92       return YES;
94     // Only allow newline on a non-empty line.
95     NSRange lineRange = [[textView string] lineRangeForRange:selectionRange];
96     if (lineRange.length == 0)
97       return YES;
99     // Insert a line-break character without ending editing.
100     [textView insertNewlineIgnoringFieldEditor:self];
102     [self resizeToText];
103     return YES;
104   }
105   return NO;
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])
125     [self resizeToText];
128 - (NSString*)defaultValue {
129   return [[self cell] defaultValue];
132 - (void)setDefaultValue:(NSString*)defaultValue {
133   [[self cell] setDefaultValue:defaultValue];
136 - (BOOL)isDefault {
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]];
149 - (BOOL)invalid {
150   return [validityMessage_ length] != 0;
153 @end
156 @implementation AutofillTextFieldCell
158 @synthesize invalid = invalid_;
159 @synthesize defaultValue = defaultValue_;
160 @synthesize decorationSize = decorationSize_;
162 - (void)setInvalid:(BOOL)invalid {
163   invalid_ = invalid;
164   [[self controlView] setNeedsDisplay:YES];
167 - (NSImage*) icon{
168   return icon_;
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;
193   }
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);
200     return textFrame;
201   }
202   return frame;
205 - (NSRect)decorationFrameForFrame:(NSRect)frame {
206   NSRect decorationFrame;
207   if (decorationSize_.width > 0) {
208     NSRect textFrame;
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);
214   }
215   return decorationFrame;
218 - (NSSize)cellSize {
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);
224   }
225   cellSize.height = std::max(cellSize.height, kMinimumHeight);
226   return cellSize;
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]
235                 inView:controlView
236                 editor:editor
237               delegate:delegate
238                  event:event];
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]
248                   inView:controlView
249                   editor:editor
250                 delegate:delegate
251                    start:start
252                   length:length];
255 - (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView*)controlView {
256   NSRect textFrame = [self textFrameForFrame:cellFrame];
257   [super drawInteriorWithFrame:textFrame inView:controlView];
259   if (icon_) {
260     NSRect iconFrame = [self decorationFrameForFrame:cellFrame];
261     [icon_ drawInRect:iconFrame
262              fromRect:NSZeroRect
263             operation:NSCompositeSourceOver
264              fraction:1.0
265        respectFlipped:YES
266                 hints:nil];
267   }
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))
273     return;
275   [super drawWithFrame:cellFrame inView:controlView];
277   if (invalid_) {
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];
283   }
286 @end