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