Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / ui / cocoa / autofill / autofill_dialog_window_controller.mm
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.
4
5 #import "chrome/browser/ui/cocoa/autofill/autofill_dialog_window_controller.h"
6
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_loading_shield_controller.h"
16 #import "chrome/browser/ui/cocoa/autofill/autofill_main_container.h"
17 #import "chrome/browser/ui/cocoa/autofill/autofill_overlay_controller.h"
18 #import "chrome/browser/ui/cocoa/autofill/autofill_section_container.h"
19 #import "chrome/browser/ui/cocoa/autofill/autofill_sign_in_container.h"
20 #import "chrome/browser/ui/cocoa/autofill/autofill_textfield.h"
21 #import "chrome/browser/ui/cocoa/constrained_window/constrained_window_custom_window.h"
22 #include "content/public/browser/web_contents.h"
23 #import "ui/base/cocoa/flipped_view.h"
24 #include "ui/base/cocoa/window_size_constants.h"
25
26 // The minimum useful height of the contents area of the dialog.
27 const CGFloat kMinimumContentsHeight = 101;
28
29 #pragma mark AutofillDialogWindow
30
31 // Window class for the AutofillDialog. Its main purpose is the proper handling
32 // of layout requests - i.e. ensuring that layout is fully done before any
33 // updates of the display happen.
34 @interface AutofillDialogWindow : ConstrainedWindowCustomWindow {
35  @private
36   BOOL needsLayout_;  // Indicates that the subviews need to be laid out.
37 }
38
39 // Request a new layout for all subviews. Layout occurs right before -display
40 // or -displayIfNeeded are invoked.
41 - (void)requestRelayout;
42
43 // Layout the window's subviews. Delegates to the controller.
44 - (void)performLayout;
45
46 @end
47
48
49 @implementation AutofillDialogWindow
50
51 - (void)requestRelayout {
52   needsLayout_ = YES;
53
54   // Ensure displayIfNeeded: is sent on the next pass through the event loop.
55   [self setViewsNeedDisplay:YES];
56 }
57
58 - (void)performLayout {
59   if (needsLayout_) {
60     needsLayout_ = NO;
61     AutofillDialogWindowController* controller =
62         base::mac::ObjCCastStrict<AutofillDialogWindowController>(
63             [self windowController]);
64     [controller performLayout];
65   }
66 }
67
68 - (void)display {
69   [self performLayout];
70   [super display];
71 }
72
73 - (void)displayIfNeeded {
74   [self performLayout];
75   [super displayIfNeeded];
76 }
77
78 @end
79
80 #pragma mark Field Editor
81
82 @interface AutofillDialogFieldEditor : NSTextView
83 @end
84
85
86 @implementation AutofillDialogFieldEditor
87
88 - (void)mouseDown:(NSEvent*)event {
89   // Delegate _must_ be notified before mouseDown is complete, since it needs
90   // to distinguish between mouseDown for already focused fields, and fields
91   // that will receive focus as part of the mouseDown.
92   AutofillTextField* textfield =
93       base::mac::ObjCCastStrict<AutofillTextField>([self delegate]);
94   [textfield onEditorMouseDown:self];
95   [super mouseDown:event];
96 }
97
98 // Intercept key down messages and forward them to the text fields delegate.
99 // This needs to happen in the field editor, since it handles all keyDown
100 // messages for NSTextField.
101 - (void)keyDown:(NSEvent*)event {
102   AutofillTextField* textfield =
103       base::mac::ObjCCastStrict<AutofillTextField>([self delegate]);
104   if ([[textfield inputDelegate] keyEvent:event
105                                  forInput:textfield] != kKeyEventHandled) {
106     [super keyDown:event];
107   }
108 }
109
110 @end
111
112
113 #pragma mark Window Controller
114
115 @interface AutofillDialogWindowController ()
116
117 // Compute maximum allowed height for the dialog.
118 - (CGFloat)maxHeight;
119
120 // Update size constraints on sign-in container.
121 - (void)updateSignInSizeConstraints;
122
123 // Notification that the WebContent's view frame has changed.
124 - (void)onContentViewFrameDidChange:(NSNotification*)notification;
125
126 // Update whether or not the main container is hidden.
127 - (void)updateMainContainerVisibility;
128
129 - (AutofillDialogWindow*)autofillWindow;
130
131 @end
132
133
134 @implementation AutofillDialogWindowController (NSWindowDelegate)
135
136 - (id)windowWillReturnFieldEditor:(NSWindow*)window toObject:(id)client {
137   AutofillTextField* textfield = base::mac::ObjCCast<AutofillTextField>(client);
138   if (!textfield)
139     return nil;
140
141   if (!fieldEditor_) {
142     fieldEditor_.reset([[AutofillDialogFieldEditor alloc] init]);
143     [fieldEditor_ setFieldEditor:YES];
144   }
145   return fieldEditor_.get();
146 }
147
148 @end
149
150
151 @implementation AutofillDialogWindowController
152
153 - (id)initWithWebContents:(content::WebContents*)webContents
154                    dialog:(autofill::AutofillDialogCocoa*)dialog {
155   DCHECK(webContents);
156
157   base::scoped_nsobject<ConstrainedWindowCustomWindow> window(
158       [[AutofillDialogWindow alloc]
159           initWithContentRect:ui::kWindowSizeDeterminedLater]);
160
161   if ((self = [super initWithWindow:window])) {
162     [window setDelegate:self];
163     webContents_ = webContents;
164     dialog_ = dialog;
165
166     header_.reset([[AutofillHeader alloc] initWithDelegate:dialog->delegate()]);
167
168     mainContainer_.reset([[AutofillMainContainer alloc]
169                              initWithDelegate:dialog->delegate()]);
170     [mainContainer_ setTarget:self];
171
172     signInContainer_.reset(
173         [[AutofillSignInContainer alloc] initWithDialog:dialog]);
174     [[signInContainer_ view] setHidden:YES];
175
176     loadingShieldController_.reset(
177         [[AutofillLoadingShieldController alloc] initWithDelegate:
178             dialog->delegate()]);
179     [[loadingShieldController_ view] setHidden:YES];
180
181     overlayController_.reset(
182         [[AutofillOverlayController alloc] initWithDelegate:
183             dialog->delegate()]);
184     [[overlayController_ view] setHidden:YES];
185
186     // This needs a flipped content view because otherwise the size
187     // animation looks odd. However, replacing the contentView for constrained
188     // windows does not work - it does custom rendering.
189     base::scoped_nsobject<NSView> flippedContentView(
190         [[FlippedView alloc] initWithFrame:
191             [[[self window] contentView] frame]]);
192     [flippedContentView setSubviews:
193         @[[header_ view],
194           [mainContainer_ view],
195           [signInContainer_ view],
196           [loadingShieldController_ view],
197           [overlayController_ view]]];
198     [flippedContentView setAutoresizingMask:
199         (NSViewWidthSizable | NSViewHeightSizable)];
200     [[[self window] contentView] addSubview:flippedContentView];
201     [mainContainer_ setAnchorView:[header_ anchorView]];
202   }
203   return self;
204 }
205
206 - (void)dealloc {
207   [[NSNotificationCenter defaultCenter] removeObserver:self];
208   [super dealloc];
209 }
210
211 - (CGFloat)maxHeight {
212   NSRect dialogFrameRect = [[self window] frame];
213   NSRect browserFrameRect = [webContents_->GetTopLevelNativeWindow() frame];
214   dialogFrameRect.size.height =
215       NSMaxY(dialogFrameRect) - NSMinY(browserFrameRect);
216   dialogFrameRect = [[self window] contentRectForFrameRect:dialogFrameRect];
217   return NSHeight(dialogFrameRect);
218 }
219
220 - (void)updateSignInSizeConstraints {
221   // For the minimum height, account for the size of the footer. Even though the
222   // footer will not be visible when the sign-in view is showing, this prevents
223   // the dialog's size from bouncing around.
224   CGFloat width = NSWidth([[[self window] contentView] frame]);
225   CGFloat minHeight =
226       kMinimumContentsHeight +
227       [mainContainer_ decorationSizeForWidth:width].height;
228
229   // For the maximum size, factor in the size of the header.
230   CGFloat headerHeight = [[header_ view] frame].size.height;
231   CGFloat maxHeight = std::max([self maxHeight] - headerHeight, minHeight);
232
233   [signInContainer_ constrainSizeToMinimum:NSMakeSize(width, minHeight)
234                                    maximum:NSMakeSize(width, maxHeight)];
235 }
236
237 - (void)onContentViewFrameDidChange:(NSNotification*)notification {
238   [self updateSignInSizeConstraints];
239   if ([[signInContainer_ view] isHidden])
240     [self requestRelayout];
241 }
242
243 - (void)updateMainContainerVisibility {
244   BOOL visible =
245       [[loadingShieldController_ view] isHidden] &&
246       [[overlayController_ view] isHidden] &&
247       [[signInContainer_ view] isHidden];
248   BOOL wasVisible = ![[mainContainer_ view] isHidden];
249   [[mainContainer_ view] setHidden:!visible];
250
251   // Postpone [mainContainer_ didBecomeVisible] until layout is complete.
252   if (visible && !wasVisible) {
253     mainContainerBecameVisible_ = YES;
254     [self requestRelayout];
255   }
256 }
257
258 - (AutofillDialogWindow*)autofillWindow {
259   return base::mac::ObjCCastStrict<AutofillDialogWindow>([self window]);
260 }
261
262 - (void)requestRelayout {
263   [[self autofillWindow] requestRelayout];
264 }
265
266 - (NSSize)preferredSize {
267   NSSize size;
268
269   if (![[overlayController_ view] isHidden]) {
270     // Overlay never changes window width.
271     size.width = NSWidth([[[self window] contentView] frame]);
272     size.height = [overlayController_ heightForWidth:size.width];
273   } else {
274     // Overall size is determined by either main container or sign in view.
275     if ([[signInContainer_ view] isHidden])
276       size = [mainContainer_ preferredSize];
277     else
278       size = [signInContainer_ preferredSize];
279
280     // Always make room for the header.
281     CGFloat headerHeight = [header_ preferredSize].height;
282     size.height += headerHeight;
283
284     // For the minimum height, account for both the header and the footer. Even
285     // though the footer will not be visible when the sign-in view is showing,
286     // this prevents the dialog's size from bouncing around.
287     CGFloat minHeight = kMinimumContentsHeight;
288     minHeight += [mainContainer_ decorationSizeForWidth:size.width].height;
289     minHeight += headerHeight;
290
291     // Show as much of the main view as is possible without going past the
292     // bottom of the browser window, unless this would cause the dialog to be
293     // less tall than the minimum height.
294     size.height = std::min(size.height, [self maxHeight]);
295     size.height = std::max(size.height, minHeight);
296   }
297
298   return size;
299 }
300
301 - (void)performLayout {
302   NSRect contentRect = NSZeroRect;
303   contentRect.size = [self preferredSize];
304
305   CGFloat headerHeight = [header_ preferredSize].height;
306   NSRect headerRect, mainRect;
307   NSDivideRect(contentRect, &headerRect, &mainRect, headerHeight, NSMinYEdge);
308
309   [[header_ view] setFrame:headerRect];
310   [header_ performLayout];
311
312   if ([[signInContainer_ view] isHidden]) {
313     [[mainContainer_ view] setFrame:mainRect];
314     [mainContainer_ performLayout];
315   } else {
316     [[signInContainer_ view] setFrame:mainRect];
317   }
318
319   [[loadingShieldController_ view] setFrame:contentRect];
320   [loadingShieldController_ performLayout];
321
322   [[overlayController_ view] setFrame:contentRect];
323   [overlayController_ performLayout];
324
325   NSRect frameRect = [[self window] frameRectForContentRect:contentRect];
326   [[self window] setFrame:frameRect display:YES];
327
328   [[self window] recalculateKeyViewLoop];
329
330   if (mainContainerBecameVisible_) {
331     [mainContainer_ scrollInitialEditorIntoViewAndMakeFirstResponder];
332     mainContainerBecameVisible_ = NO;
333   }
334 }
335
336 - (IBAction)accept:(id)sender {
337   if ([mainContainer_ validate])
338     dialog_->delegate()->OnAccept();
339   else
340     [mainContainer_ makeFirstInvalidInputFirstResponder];
341 }
342
343 - (IBAction)cancel:(id)sender {
344   dialog_->delegate()->OnCancel();
345   dialog_->PerformClose();
346 }
347
348 - (void)show {
349   // Resizing the browser causes the ConstrainedWindow to move.
350   // Observe that to allow resizes based on browser size.
351   // NOTE: This MUST come last after all initial setup is done, because there
352   // is an immediate notification post registration.
353   DCHECK([self window]);
354   [[NSNotificationCenter defaultCenter]
355       addObserver:self
356          selector:@selector(onContentViewFrameDidChange:)
357              name:NSWindowDidMoveNotification
358            object:[self window]];
359
360   [self updateAccountChooser];
361   [self updateNotificationArea];
362   [self requestRelayout];
363 }
364
365 - (void)hide {
366   dialog_->delegate()->OnCancel();
367   dialog_->PerformClose();
368 }
369
370 - (void)updateNotificationArea {
371   [mainContainer_ updateNotificationArea];
372 }
373
374 - (void)updateAccountChooser {
375   [header_ update];
376   [mainContainer_ updateLegalDocuments];
377   [loadingShieldController_ update];
378   [self updateMainContainerVisibility];
379 }
380
381 - (void)updateButtonStrip {
382   // For the duration of the overlay, hide the main contents and the header.
383   // This prevents the currently focused text field "shining through". No need
384   // to remember previous state, because the overlay view is always the last
385   // state of the dialog.
386   [overlayController_ updateState];
387   [[header_ view] setHidden:![[overlayController_ view] isHidden]];
388   [self updateMainContainerVisibility];
389 }
390
391 - (void)updateSection:(autofill::DialogSection)section {
392   [[mainContainer_ sectionForId:section] update];
393   [mainContainer_ updateSaveInChrome];
394 }
395
396 - (void)fillSection:(autofill::DialogSection)section
397             forType:(autofill::ServerFieldType)type {
398   [[mainContainer_ sectionForId:section] fillForType:type];
399   [mainContainer_ updateSaveInChrome];
400 }
401
402 - (void)updateForErrors {
403   [mainContainer_ validate];
404 }
405
406 - (content::NavigationController*)showSignIn:(const GURL&)url {
407   [self updateSignInSizeConstraints];
408   // Ensure |signInContainer_| is set to the same size as |mainContainer_|, to
409   // force its minimum size so that there will not be a resize until the
410   // contents are loaded.
411   [[signInContainer_ view] setFrameSize:[[mainContainer_ view] frame].size];
412   [signInContainer_ loadSignInPage:url];
413
414   [[signInContainer_ view] setHidden:NO];
415   [self updateMainContainerVisibility];
416   [self requestRelayout];
417
418   return [signInContainer_ navigationController];
419 }
420
421 - (void)getInputs:(autofill::FieldValueMap*)output
422        forSection:(autofill::DialogSection)section {
423   [[mainContainer_ sectionForId:section] getInputs:output];
424 }
425
426 - (NSString*)getCvc {
427   autofill::DialogSection section = autofill::SECTION_CC;
428   NSString* value = [[mainContainer_ sectionForId:section] suggestionText];
429   if (!value) {
430     section = autofill::SECTION_CC_BILLING;
431     value = [[mainContainer_ sectionForId:section] suggestionText];
432   }
433   return value;
434 }
435
436 - (BOOL)saveDetailsLocally {
437   return [mainContainer_ saveDetailsLocally];
438 }
439
440 - (void)hideSignIn {
441   [[signInContainer_ view] setHidden:YES];
442   [self updateMainContainerVisibility];
443   [self requestRelayout];
444 }
445
446 - (void)modelChanged {
447   [mainContainer_ modelChanged];
448 }
449
450 - (void)updateErrorBubble {
451   [mainContainer_ updateErrorBubble];
452 }
453
454 - (void)onSignInResize:(NSSize)size {
455   [signInContainer_ setPreferredSize:size];
456   [self requestRelayout];
457 }
458
459 - (void)validateSection:(autofill::DialogSection)section {
460   [[mainContainer_ sectionForId:section] validateFor:autofill::VALIDATE_EDIT];
461 }
462
463 @end