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.
4
5#import "chrome/browser/ui/cocoa/autofill/autofill_textfield.h"
6
7#include <algorithm>
8#include <cmath>
9
10#include "base/logging.h"
11#include "ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h"
12
13const CGFloat kGap = 6.0;  // gap between icon and text.
14const CGFloat kMinimumHeight = 27.0;  // Enforced minimum height for text cells.
15
16@interface AutofillTextFieldCell ()
17- (NSRect)textFrameForFrame:(NSRect)frame;
18@end
19
20@interface AutofillTextField ()
21// Resize to accommodate contents, but keep width fixed.
22- (void)resizeToText;
23@end
24
25@implementation AutofillTextField
26
27@synthesize inputDelegate = inputDelegate_;
28@synthesize isMultiline = isMultiline_;
29
30+ (Class)cellClass {
31  return [AutofillTextFieldCell class];
32}
33
34- (id)initWithFrame:(NSRect)frame {
35  if (self = [super initWithFrame:frame])
36    [super setDelegate:self];
37  return self;
38}
39
40- (BOOL)becomeFirstResponder {
41  BOOL result = [super becomeFirstResponder];
42  if (result && inputDelegate_) {
43    [inputDelegate_ fieldBecameFirstResponder:self];
44    shouldFilterClick_ = YES;
45  }
46  return result;
47}
48
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];
54}
55
56- (NSRect)decorationFrame {
57  return [[self cell] decorationFrameForFrame:[self frame]];
58}
59
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;
68}
69
70- (void)controlTextDidEndEditing:(NSNotification*)notification {
71  if (inputDelegate_)
72    [inputDelegate_ didEndEditing:self];
73}
74
75- (void)controlTextDidChange:(NSNotification*)aNotification {
76  if (inputDelegate_)
77    [inputDelegate_ didChange:self];
78}
79
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;
86
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;
93
94    // Only allow newline on a non-empty line.
95    NSRange lineRange = [[textView string] lineRangeForRange:selectionRange];
96    if (lineRange.length == 0)
97      return YES;
98
99    // Insert a line-break character without ending editing.
100    [textView insertNewlineIgnoringFieldEditor:self];
101
102    [self resizeToText];
103    return YES;
104  }
105  return NO;
106}
107
108- (void)resizeToText {
109  NSSize size = [[self cell] cellSize];
110  size.width = NSWidth([self frame]);
111  [self setFrameSize:size];
112
113  id delegate = [[self window] windowController];
114  if ([delegate respondsToSelector:@selector(requestRelayout)])
115    [delegate performSelector:@selector(requestRelayout)];
116}
117
118- (NSString*)fieldValue {
119  return [[self cell] fieldValue];
120}
121
122- (void)setFieldValue:(NSString*)fieldValue {
123  [[self cell] setFieldValue:fieldValue];
124  if ([self isMultiline])
125    [self resizeToText];
126}
127
128- (NSString*)defaultValue {
129  return [[self cell] defaultValue];
130}
131
132- (void)setDefaultValue:(NSString*)defaultValue {
133  [[self cell] setDefaultValue:defaultValue];
134}
135
136- (BOOL)isDefault {
137  return [[[self cell] fieldValue] isEqualToString:[[self cell] defaultValue]];
138}
139
140- (NSString*)validityMessage {
141  return validityMessage_;
142}
143
144- (void)setValidityMessage:(NSString*)validityMessage {
145  validityMessage_.reset([validityMessage copy]);
146  [[self cell] setInvalid:[self invalid]];
147}
148
149- (BOOL)invalid {
150  return [validityMessage_ length] != 0;
151}
152
153@end
154
155
156@implementation AutofillTextFieldCell
157
158@synthesize invalid = invalid_;
159@synthesize defaultValue = defaultValue_;
160@synthesize decorationSize = decorationSize_;
161
162- (void)setInvalid:(BOOL)invalid {
163  invalid_ = invalid;
164  [[self controlView] setNeedsDisplay:YES];
165}
166
167- (NSImage*) icon{
168  return icon_;
169}
170
171- (void)setIcon:(NSImage*)icon {
172  icon_.reset([icon retain]);
173  [self setDecorationSize:[icon_ size]];
174  [[self controlView] setNeedsDisplay:YES];
175}
176
177- (NSString*)fieldValue {
178  return [self stringValue];
179}
180
181- (void)setFieldValue:(NSString*)fieldValue {
182  [self setStringValue:fieldValue];
183}
184
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));
195
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;
203}
204
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;
216}
217
218- (NSSize)cellSize {
219  NSSize cellSize = [super cellSize];
220
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;
227}
228
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];
239}
240
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];
253}
254
255- (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView*)controlView {
256  NSRect textFrame = [self textFrameForFrame:cellFrame];
257  [super drawInteriorWithFrame:textFrame inView:controlView];
258
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  }
268}
269
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;
274
275  [super drawWithFrame:cellFrame inView:controlView];
276
277  if (invalid_) {
278    gfx::ScopedNSGraphicsContextSaveGState state;
279
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  }
284}
285
286@end
287