Home | History | Annotate | Download | only in autofill
      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