OLD | NEW |
| (Empty) |
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_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_wi
ndow.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" | |
22 | |
23 // The minimum useful height of the contents area of the dialog. | |
24 const CGFloat kMinimumContentsHeight = 101; | |
25 | |
26 #pragma mark AutofillDialogWindow | |
27 | |
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. | |
34 } | |
35 | |
36 // Request a new layout for all subviews. Layout occurs right before -display | |
37 // or -displayIfNeeded are invoked. | |
38 - (void)requestRelayout; | |
39 | |
40 // Layout the window's subviews. Delegates to the controller. | |
41 - (void)performLayout; | |
42 | |
43 @end | |
44 | |
45 | |
46 @implementation AutofillDialogWindow | |
47 | |
48 - (void)requestRelayout { | |
49 needsLayout_ = YES; | |
50 | |
51 // Ensure displayIfNeeded: is sent on the next pass through the event loop. | |
52 [self setViewsNeedDisplay:YES]; | |
53 } | |
54 | |
55 - (void)performLayout { | |
56 if (needsLayout_) { | |
57 needsLayout_ = NO; | |
58 AutofillDialogWindowController* controller = | |
59 base::mac::ObjCCastStrict<AutofillDialogWindowController>( | |
60 [self windowController]); | |
61 [controller performLayout]; | |
62 } | |
63 } | |
64 | |
65 - (void)display { | |
66 [self performLayout]; | |
67 [super display]; | |
68 } | |
69 | |
70 - (void)displayIfNeeded { | |
71 [self performLayout]; | |
72 [super displayIfNeeded]; | |
73 } | |
74 | |
75 @end | |
76 | |
77 #pragma mark Field Editor | |
78 | |
79 @interface AutofillDialogFieldEditor : NSTextView | |
80 @end | |
81 | |
82 | |
83 @implementation AutofillDialogFieldEditor | |
84 | |
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]; | |
93 } | |
94 | |
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 } | |
105 } | |
106 | |
107 @end | |
108 | |
109 | |
110 #pragma mark Window Controller | |
111 | |
112 @interface AutofillDialogWindowController () | |
113 | |
114 // Compute maximum allowed height for the dialog. | |
115 - (CGFloat)maxHeight; | |
116 | |
117 // Notification that the WebContent's view frame has changed. | |
118 - (void)onContentViewFrameDidChange:(NSNotification*)notification; | |
119 | |
120 - (AutofillDialogWindow*)autofillWindow; | |
121 | |
122 @end | |
123 | |
124 | |
125 @implementation AutofillDialogWindowController (NSWindowDelegate) | |
126 | |
127 - (id)windowWillReturnFieldEditor:(NSWindow*)window toObject:(id)client { | |
128 AutofillTextField* textfield = base::mac::ObjCCast<AutofillTextField>(client); | |
129 if (!textfield) | |
130 return nil; | |
131 | |
132 if (!fieldEditor_) { | |
133 fieldEditor_.reset([[AutofillDialogFieldEditor alloc] init]); | |
134 [fieldEditor_ setFieldEditor:YES]; | |
135 } | |
136 return fieldEditor_.get(); | |
137 } | |
138 | |
139 @end | |
140 | |
141 | |
142 @implementation AutofillDialogWindowController | |
143 | |
144 - (id)initWithWebContents:(content::WebContents*)webContents | |
145 dialog:(autofill::AutofillDialogCocoa*)dialog { | |
146 DCHECK(webContents); | |
147 | |
148 base::scoped_nsobject<ConstrainedWindowCustomWindow> window( | |
149 [[AutofillDialogWindow alloc] | |
150 initWithContentRect:ui::kWindowSizeDeterminedLater]); | |
151 | |
152 if ((self = [super initWithWindow:window])) { | |
153 [window setDelegate:self]; | |
154 webContents_ = webContents; | |
155 dialog_ = dialog; | |
156 | |
157 header_.reset([[AutofillHeader alloc] initWithDelegate:dialog->delegate()]); | |
158 | |
159 mainContainer_.reset([[AutofillMainContainer alloc] | |
160 initWithDelegate:dialog->delegate()]); | |
161 [mainContainer_ setTarget:self]; | |
162 | |
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; | |
175 } | |
176 | |
177 - (void)dealloc { | |
178 [[NSNotificationCenter defaultCenter] removeObserver:self]; | |
179 [super dealloc]; | |
180 } | |
181 | |
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); | |
189 } | |
190 | |
191 - (void)onContentViewFrameDidChange:(NSNotification*)notification { | |
192 [self requestRelayout]; | |
193 } | |
194 | |
195 - (AutofillDialogWindow*)autofillWindow { | |
196 return base::mac::ObjCCastStrict<AutofillDialogWindow>([self window]); | |
197 } | |
198 | |
199 - (void)requestRelayout { | |
200 [[self autofillWindow] requestRelayout]; | |
201 } | |
202 | |
203 - (NSSize)preferredSize { | |
204 NSSize size; | |
205 | |
206 size = [mainContainer_ preferredSize]; | |
207 | |
208 // Always make room for the header. | |
209 CGFloat headerHeight = [header_ preferredSize].height; | |
210 size.height += headerHeight; | |
211 | |
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; | |
218 | |
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); | |
224 | |
225 return size; | |
226 } | |
227 | |
228 - (void)performLayout { | |
229 NSRect contentRect = NSZeroRect; | |
230 contentRect.size = [self preferredSize]; | |
231 | |
232 CGFloat headerHeight = [header_ preferredSize].height; | |
233 NSRect headerRect, mainRect; | |
234 NSDivideRect(contentRect, &headerRect, &mainRect, headerHeight, NSMinYEdge); | |
235 | |
236 [[header_ view] setFrame:headerRect]; | |
237 [header_ performLayout]; | |
238 | |
239 [[mainContainer_ view] setFrame:mainRect]; | |
240 [mainContainer_ performLayout]; | |
241 | |
242 NSRect frameRect = [[self window] frameRectForContentRect:contentRect]; | |
243 [[self window] setFrame:frameRect display:YES]; | |
244 | |
245 [[self window] recalculateKeyViewLoop]; | |
246 | |
247 if (mainContainerBecameVisible_) { | |
248 [mainContainer_ scrollInitialEditorIntoViewAndMakeFirstResponder]; | |
249 mainContainerBecameVisible_ = NO; | |
250 } | |
251 } | |
252 | |
253 - (IBAction)accept:(id)sender { | |
254 if ([mainContainer_ validate]) | |
255 dialog_->delegate()->OnAccept(); | |
256 else | |
257 [mainContainer_ makeFirstInvalidInputFirstResponder]; | |
258 } | |
259 | |
260 - (IBAction)cancel:(id)sender { | |
261 dialog_->delegate()->OnCancel(); | |
262 dialog_->PerformClose(); | |
263 } | |
264 | |
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]]; | |
276 | |
277 [self updateNotificationArea]; | |
278 [self requestRelayout]; | |
279 } | |
280 | |
281 - (void)hide { | |
282 dialog_->delegate()->OnCancel(); | |
283 dialog_->PerformClose(); | |
284 } | |
285 | |
286 - (void)updateNotificationArea { | |
287 [mainContainer_ updateNotificationArea]; | |
288 } | |
289 | |
290 - (void)updateSection:(autofill::DialogSection)section { | |
291 [[mainContainer_ sectionForId:section] update]; | |
292 [mainContainer_ updateSaveInChrome]; | |
293 } | |
294 | |
295 - (void)fillSection:(autofill::DialogSection)section | |
296 forType:(autofill::ServerFieldType)type { | |
297 [[mainContainer_ sectionForId:section] fillForType:type]; | |
298 [mainContainer_ updateSaveInChrome]; | |
299 } | |
300 | |
301 - (void)updateForErrors { | |
302 [mainContainer_ validate]; | |
303 } | |
304 | |
305 - (void)getInputs:(autofill::FieldValueMap*)output | |
306 forSection:(autofill::DialogSection)section { | |
307 [[mainContainer_ sectionForId:section] getInputs:output]; | |
308 } | |
309 | |
310 - (NSString*)getCvc { | |
311 return [[mainContainer_ sectionForId:autofill::SECTION_CC] suggestionText]; | |
312 } | |
313 | |
314 - (BOOL)saveDetailsLocally { | |
315 return [mainContainer_ saveDetailsLocally]; | |
316 } | |
317 | |
318 - (void)modelChanged { | |
319 [mainContainer_ modelChanged]; | |
320 } | |
321 | |
322 - (void)updateErrorBubble { | |
323 [mainContainer_ updateErrorBubble]; | |
324 } | |
325 | |
326 - (void)validateSection:(autofill::DialogSection)section { | |
327 [[mainContainer_ sectionForId:section] validateFor:autofill::VALIDATE_EDIT]; | |
328 } | |
329 | |
330 @end | |
OLD | NEW |