Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / chrome / browser / ui / cocoa / autofill / autofill_dialog_window_controller.mm
blobf81fce4857321318a9159d3070250c096367eace
1 // Copyright 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_dialog_window_controller.h"
7 #include "base/mac/foundation_util.h"
8 #include "base/mac/scoped_nsobject.h"
9 #include "base/strings/sys_string_conversions.h"
10 #include "chrome/browser/ui/autofill/autofill_dialog_view_delegate.h"
11 #include "chrome/browser/ui/cocoa/autofill/autofill_dialog_cocoa.h"
12 #include "chrome/browser/ui/cocoa/autofill/autofill_dialog_constants.h"
13 #import "chrome/browser/ui/cocoa/autofill/autofill_header.h"
14 #import "chrome/browser/ui/cocoa/autofill/autofill_input_field.h"
15 #import "chrome/browser/ui/cocoa/autofill/autofill_main_container.h"
16 #import "chrome/browser/ui/cocoa/autofill/autofill_section_container.h"
17 #import "chrome/browser/ui/cocoa/autofill/autofill_textfield.h"
18 #import "chrome/browser/ui/cocoa/constrained_window/constrained_window_custom_window.h"
19 #include "content/public/browser/web_contents.h"
20 #import "ui/base/cocoa/flipped_view.h"
21 #include "ui/base/cocoa/window_size_constants.h"
23 // The minimum useful height of the contents area of the dialog.
24 const CGFloat kMinimumContentsHeight = 101;
26 #pragma mark AutofillDialogWindow
28 // Window class for the AutofillDialog. Its main purpose is the proper handling
29 // of layout requests - i.e. ensuring that layout is fully done before any
30 // updates of the display happen.
31 @interface AutofillDialogWindow : ConstrainedWindowCustomWindow {
32  @private
33   BOOL needsLayout_;  // Indicates that the subviews need to be laid out.
36 // Request a new layout for all subviews. Layout occurs right before -display
37 // or -displayIfNeeded are invoked.
38 - (void)requestRelayout;
40 // Layout the window's subviews. Delegates to the controller.
41 - (void)performLayout;
43 @end
46 @implementation AutofillDialogWindow
48 - (void)requestRelayout {
49   needsLayout_ = YES;
51   // Ensure displayIfNeeded: is sent on the next pass through the event loop.
52   [self setViewsNeedDisplay:YES];
55 - (void)performLayout {
56   if (needsLayout_) {
57     needsLayout_ = NO;
58     AutofillDialogWindowController* controller =
59         base::mac::ObjCCastStrict<AutofillDialogWindowController>(
60             [self windowController]);
61     [controller performLayout];
62   }
65 - (void)display {
66   [self performLayout];
67   [super display];
70 - (void)displayIfNeeded {
71   [self performLayout];
72   [super displayIfNeeded];
75 @end
77 #pragma mark Field Editor
79 @interface AutofillDialogFieldEditor : NSTextView
80 @end
83 @implementation AutofillDialogFieldEditor
85 - (void)mouseDown:(NSEvent*)event {
86   // Delegate _must_ be notified before mouseDown is complete, since it needs
87   // to distinguish between mouseDown for already focused fields, and fields
88   // that will receive focus as part of the mouseDown.
89   AutofillTextField* textfield =
90       base::mac::ObjCCastStrict<AutofillTextField>([self delegate]);
91   [textfield onEditorMouseDown:self];
92   [super mouseDown:event];
95 // Intercept key down messages and forward them to the text fields delegate.
96 // This needs to happen in the field editor, since it handles all keyDown
97 // messages for NSTextField.
98 - (void)keyDown:(NSEvent*)event {
99   AutofillTextField* textfield =
100       base::mac::ObjCCastStrict<AutofillTextField>([self delegate]);
101   if ([[textfield inputDelegate] keyEvent:event
102                                  forInput:textfield] != kKeyEventHandled) {
103     [super keyDown:event];
104   }
107 @end
110 #pragma mark Window Controller
112 @interface AutofillDialogWindowController ()
114 // Compute maximum allowed height for the dialog.
115 - (CGFloat)maxHeight;
117 // Notification that the WebContent's view frame has changed.
118 - (void)onContentViewFrameDidChange:(NSNotification*)notification;
120 - (AutofillDialogWindow*)autofillWindow;
122 @end
125 @implementation AutofillDialogWindowController (NSWindowDelegate)
127 - (id)windowWillReturnFieldEditor:(NSWindow*)window toObject:(id)client {
128   AutofillTextField* textfield = base::mac::ObjCCast<AutofillTextField>(client);
129   if (!textfield)
130     return nil;
132   if (!fieldEditor_) {
133     fieldEditor_.reset([[AutofillDialogFieldEditor alloc] init]);
134     [fieldEditor_ setFieldEditor:YES];
135   }
136   return fieldEditor_.get();
139 @end
142 @implementation AutofillDialogWindowController
144 - (id)initWithWebContents:(content::WebContents*)webContents
145                    dialog:(autofill::AutofillDialogCocoa*)dialog {
146   DCHECK(webContents);
148   base::scoped_nsobject<ConstrainedWindowCustomWindow> window(
149       [[AutofillDialogWindow alloc]
150           initWithContentRect:ui::kWindowSizeDeterminedLater]);
152   if ((self = [super initWithWindow:window])) {
153     [window setDelegate:self];
154     webContents_ = webContents;
155     dialog_ = dialog;
157     header_.reset([[AutofillHeader alloc] initWithDelegate:dialog->delegate()]);
159     mainContainer_.reset([[AutofillMainContainer alloc]
160                              initWithDelegate:dialog->delegate()]);
161     [mainContainer_ setTarget:self];
163     // This needs a flipped content view because otherwise the size
164     // animation looks odd. However, replacing the contentView for constrained
165     // windows does not work - it does custom rendering.
166     base::scoped_nsobject<NSView> flippedContentView(
167         [[FlippedView alloc] initWithFrame:
168             [[[self window] contentView] frame]]);
169     [flippedContentView setSubviews:@[ [header_ view], [mainContainer_ view] ]];
170     [flippedContentView setAutoresizingMask:
171         (NSViewWidthSizable | NSViewHeightSizable)];
172     [[[self window] contentView] addSubview:flippedContentView];
173   }
174   return self;
177 - (void)dealloc {
178   [[NSNotificationCenter defaultCenter] removeObserver:self];
179   [super dealloc];
182 - (CGFloat)maxHeight {
183   NSRect dialogFrameRect = [[self window] frame];
184   NSRect browserFrameRect = [webContents_->GetTopLevelNativeWindow() frame];
185   dialogFrameRect.size.height =
186       NSMaxY(dialogFrameRect) - NSMinY(browserFrameRect);
187   dialogFrameRect = [[self window] contentRectForFrameRect:dialogFrameRect];
188   return NSHeight(dialogFrameRect);
191 - (void)onContentViewFrameDidChange:(NSNotification*)notification {
192   [self requestRelayout];
195 - (AutofillDialogWindow*)autofillWindow {
196   return base::mac::ObjCCastStrict<AutofillDialogWindow>([self window]);
199 - (void)requestRelayout {
200   [[self autofillWindow] requestRelayout];
203 - (NSSize)preferredSize {
204   NSSize size;
206   size = [mainContainer_ preferredSize];
208   // Always make room for the header.
209   CGFloat headerHeight = [header_ preferredSize].height;
210   size.height += headerHeight;
212   // For the minimum height, account for both the header and the footer. Even
213   // though the footer will not be visible when the sign-in view is showing,
214   // this prevents the dialog's size from bouncing around.
215   CGFloat minHeight = kMinimumContentsHeight;
216   minHeight += [mainContainer_ decorationSizeForWidth:size.width].height;
217   minHeight += headerHeight;
219   // Show as much of the main view as is possible without going past the
220   // bottom of the browser window, unless this would cause the dialog to be
221   // less tall than the minimum height.
222   size.height = std::min(size.height, [self maxHeight]);
223   size.height = std::max(size.height, minHeight);
225   return size;
228 - (void)performLayout {
229   NSRect contentRect = NSZeroRect;
230   contentRect.size = [self preferredSize];
232   CGFloat headerHeight = [header_ preferredSize].height;
233   NSRect headerRect, mainRect;
234   NSDivideRect(contentRect, &headerRect, &mainRect, headerHeight, NSMinYEdge);
236   [[header_ view] setFrame:headerRect];
237   [header_ performLayout];
239   [[mainContainer_ view] setFrame:mainRect];
240   [mainContainer_ performLayout];
242   NSRect frameRect = [[self window] frameRectForContentRect:contentRect];
243   [[self window] setFrame:frameRect display:YES];
245   [[self window] recalculateKeyViewLoop];
247   if (mainContainerBecameVisible_) {
248     [mainContainer_ scrollInitialEditorIntoViewAndMakeFirstResponder];
249     mainContainerBecameVisible_ = NO;
250   }
253 - (IBAction)accept:(id)sender {
254   if ([mainContainer_ validate])
255     dialog_->delegate()->OnAccept();
256   else
257     [mainContainer_ makeFirstInvalidInputFirstResponder];
260 - (IBAction)cancel:(id)sender {
261   dialog_->delegate()->OnCancel();
262   dialog_->PerformClose();
265 - (void)show {
266   // Resizing the browser causes the ConstrainedWindow to move.
267   // Observe that to allow resizes based on browser size.
268   // NOTE: This MUST come last after all initial setup is done, because there
269   // is an immediate notification post registration.
270   DCHECK([self window]);
271   [[NSNotificationCenter defaultCenter]
272       addObserver:self
273          selector:@selector(onContentViewFrameDidChange:)
274              name:NSWindowDidMoveNotification
275            object:[self window]];
277   [self updateNotificationArea];
278   [self requestRelayout];
281 - (void)hide {
282   dialog_->delegate()->OnCancel();
283   dialog_->PerformClose();
286 - (void)updateNotificationArea {
287   [mainContainer_ updateNotificationArea];
290 - (void)updateSection:(autofill::DialogSection)section {
291   [[mainContainer_ sectionForId:section] update];
292   [mainContainer_ updateSaveInChrome];
295 - (void)fillSection:(autofill::DialogSection)section
296             forType:(autofill::ServerFieldType)type {
297   [[mainContainer_ sectionForId:section] fillForType:type];
298   [mainContainer_ updateSaveInChrome];
301 - (void)updateForErrors {
302   [mainContainer_ validate];
305 - (void)getInputs:(autofill::FieldValueMap*)output
306        forSection:(autofill::DialogSection)section {
307   [[mainContainer_ sectionForId:section] getInputs:output];
310 - (NSString*)getCvc {
311   return [[mainContainer_ sectionForId:autofill::SECTION_CC] suggestionText];
314 - (BOOL)saveDetailsLocally {
315   return [mainContainer_ saveDetailsLocally];
318 - (void)modelChanged {
319   [mainContainer_ modelChanged];
322 - (void)updateErrorBubble {
323   [mainContainer_ updateErrorBubble];
326 - (void)validateSection:(autofill::DialogSection)section {
327   [[mainContainer_ sectionForId:section] validateFor:autofill::VALIDATE_EDIT];
330 @end