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 13 const CGFloat kGap = 6.0; // gap between icon and text. 14 const 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