OLD | NEW |
1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "chrome/browser/ui/cocoa/autofill/autofill_dialog_cocoa.h" | 5 #include "chrome/browser/ui/cocoa/autofill/autofill_dialog_cocoa.h" |
6 | 6 |
7 #include "base/bind.h" | 7 #include "base/bind.h" |
8 #include "base/mac/bundle_locations.h" | |
9 #include "base/mac/foundation_util.h" | |
10 #include "base/mac/scoped_nsobject.h" | 8 #include "base/mac/scoped_nsobject.h" |
11 #include "base/message_loop/message_loop.h" | 9 #include "base/message_loop/message_loop.h" |
12 #include "base/strings/sys_string_conversions.h" | 10 #include "base/strings/sys_string_conversions.h" |
13 #include "chrome/browser/ui/autofill/autofill_dialog_view_delegate.h" | 11 #include "chrome/browser/ui/autofill/autofill_dialog_view_delegate.h" |
14 #include "chrome/browser/ui/chrome_style.h" | |
15 #import "chrome/browser/ui/cocoa/autofill/autofill_account_chooser.h" | |
16 #import "chrome/browser/ui/cocoa/autofill/autofill_details_container.h" | 12 #import "chrome/browser/ui/cocoa/autofill/autofill_details_container.h" |
17 #include "chrome/browser/ui/cocoa/autofill/autofill_dialog_constants.h" | 13 #import "chrome/browser/ui/cocoa/autofill/autofill_dialog_window_controller.h" |
18 #import "chrome/browser/ui/cocoa/autofill/autofill_input_field.h" | |
19 #import "chrome/browser/ui/cocoa/autofill/autofill_loading_shield_controller.h" | |
20 #import "chrome/browser/ui/cocoa/autofill/autofill_main_container.h" | |
21 #import "chrome/browser/ui/cocoa/autofill/autofill_overlay_controller.h" | |
22 #import "chrome/browser/ui/cocoa/autofill/autofill_section_container.h" | |
23 #import "chrome/browser/ui/cocoa/autofill/autofill_sign_in_container.h" | |
24 #import "chrome/browser/ui/cocoa/autofill/autofill_textfield.h" | |
25 #import "chrome/browser/ui/cocoa/constrained_window/constrained_window_custom_sh
eet.h" | 14 #import "chrome/browser/ui/cocoa/constrained_window/constrained_window_custom_sh
eet.h" |
26 #import "chrome/browser/ui/cocoa/constrained_window/constrained_window_custom_wi
ndow.h" | |
27 #include "content/public/browser/web_contents.h" | |
28 #include "content/public/browser/web_contents_view.h" | |
29 #include "grit/generated_resources.h" | |
30 #import "ui/base/cocoa/flipped_view.h" | |
31 #include "ui/base/cocoa/window_size_constants.h" | |
32 #include "ui/base/l10n/l10n_util.h" | |
33 #include "ui/gfx/platform_font.h" | |
34 | |
35 const CGFloat kAccountChooserHeight = 20.0; | |
36 const CGFloat kMinimumContentsHeight = 101; | |
37 | |
38 // Height of all decorations & paddings on main dialog together. | |
39 const CGFloat kDecorationHeight = kAccountChooserHeight + | |
40 autofill::kDetailVerticalPadding + | |
41 chrome_style::kClientBottomPadding + | |
42 chrome_style::kTitleTopPadding; | |
43 | 15 |
44 namespace autofill { | 16 namespace autofill { |
45 | 17 |
46 // static | 18 // static |
47 AutofillDialogView* AutofillDialogView::Create( | 19 AutofillDialogView* AutofillDialogView::Create( |
48 AutofillDialogViewDelegate* delegate) { | 20 AutofillDialogViewDelegate* delegate) { |
49 return new AutofillDialogCocoa(delegate); | 21 return new AutofillDialogCocoa(delegate); |
50 } | 22 } |
51 | 23 |
52 AutofillDialogCocoa::AutofillDialogCocoa(AutofillDialogViewDelegate* delegate) | 24 AutofillDialogCocoa::AutofillDialogCocoa(AutofillDialogViewDelegate* delegate) |
(...skipping 162 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
215 gfx::Size AutofillDialogCocoa::GetSize() const { | 187 gfx::Size AutofillDialogCocoa::GetSize() const { |
216 return gfx::Size(NSSizeToCGSize([[sheet_delegate_ window] frame].size)); | 188 return gfx::Size(NSSizeToCGSize([[sheet_delegate_ window] frame].size)); |
217 } | 189 } |
218 | 190 |
219 content::WebContents* AutofillDialogCocoa::GetSignInWebContents() { | 191 content::WebContents* AutofillDialogCocoa::GetSignInWebContents() { |
220 return [sheet_delegate_ getSignInWebContents]; | 192 return [sheet_delegate_ getSignInWebContents]; |
221 } | 193 } |
222 | 194 |
223 | 195 |
224 bool AutofillDialogCocoa::IsShowingOverlay() const { | 196 bool AutofillDialogCocoa::IsShowingOverlay() const { |
225 return [sheet_delegate_ IsShowingOverlay]; | 197 return [sheet_delegate_ isShowingOverlay]; |
226 } | 198 } |
227 | 199 |
228 void AutofillDialogCocoa::OnConstrainedWindowClosed( | 200 void AutofillDialogCocoa::OnConstrainedWindowClosed( |
229 ConstrainedWindowMac* window) { | 201 ConstrainedWindowMac* window) { |
230 constrained_window_.reset(); | 202 constrained_window_.reset(); |
231 // |this| belongs to |delegate_|, so no self-destruction here. | 203 // |this| belongs to |delegate_|, so no self-destruction here. |
232 delegate_->ViewClosed(); | 204 delegate_->ViewClosed(); |
233 } | 205 } |
234 | 206 |
235 } // autofill | 207 } // autofill |
236 | |
237 #pragma mark Field Editor | |
238 | |
239 @interface AutofillDialogFieldEditor : NSTextView | |
240 @end | |
241 | |
242 | |
243 @implementation AutofillDialogFieldEditor | |
244 | |
245 - (void)mouseDown:(NSEvent*)event { | |
246 // Delegate _must_ be notified before mouseDown is complete, since it needs | |
247 // to distinguish between mouseDown for already focused fields, and fields | |
248 // that will receive focus as part of the mouseDown. | |
249 AutofillTextField* textfield = | |
250 base::mac::ObjCCastStrict<AutofillTextField>([self delegate]); | |
251 [textfield onEditorMouseDown:self]; | |
252 [super mouseDown:event]; | |
253 } | |
254 | |
255 @end | |
256 | |
257 | |
258 #pragma mark Window Controller | |
259 | |
260 @interface AutofillDialogWindowController () | |
261 | |
262 // Compute maximum allowed height for the dialog. | |
263 - (CGFloat)maxHeight; | |
264 | |
265 // Update size constraints on sign-in container. | |
266 - (void)updateSignInSizeConstraints; | |
267 | |
268 // Notification that the WebContent's view frame has changed. | |
269 - (void)onContentViewFrameDidChange:(NSNotification*)notification; | |
270 | |
271 @end | |
272 | |
273 | |
274 @implementation AutofillDialogWindowController (NSWindowDelegate) | |
275 | |
276 - (id)windowWillReturnFieldEditor:(NSWindow*)window toObject:(id)client { | |
277 AutofillTextField* textfield = base::mac::ObjCCast<AutofillTextField>(client); | |
278 if (!textfield) | |
279 return nil; | |
280 | |
281 if (!fieldEditor_) { | |
282 fieldEditor_.reset([[AutofillDialogFieldEditor alloc] init]); | |
283 [fieldEditor_ setFieldEditor:YES]; | |
284 } | |
285 return fieldEditor_.get(); | |
286 } | |
287 | |
288 @end | |
289 | |
290 | |
291 @implementation AutofillDialogWindowController | |
292 | |
293 - (id)initWithWebContents:(content::WebContents*)webContents | |
294 autofillDialog:(autofill::AutofillDialogCocoa*)autofillDialog { | |
295 DCHECK(webContents); | |
296 | |
297 base::scoped_nsobject<ConstrainedWindowCustomWindow> window( | |
298 [[ConstrainedWindowCustomWindow alloc] | |
299 initWithContentRect:ui::kWindowSizeDeterminedLater]); | |
300 | |
301 if ((self = [super initWithWindow:window])) { | |
302 [window setDelegate:self]; | |
303 webContents_ = webContents; | |
304 autofillDialog_ = autofillDialog; | |
305 | |
306 mainContainer_.reset([[AutofillMainContainer alloc] | |
307 initWithDelegate:autofillDialog->delegate()]); | |
308 [mainContainer_ setTarget:self]; | |
309 | |
310 signInContainer_.reset( | |
311 [[AutofillSignInContainer alloc] initWithDialog:autofillDialog]); | |
312 [[signInContainer_ view] setHidden:YES]; | |
313 | |
314 // Set dialog title. | |
315 titleTextField_.reset([[NSTextField alloc] initWithFrame:NSZeroRect]); | |
316 [titleTextField_ setEditable:NO]; | |
317 [titleTextField_ setBordered:NO]; | |
318 [titleTextField_ setDrawsBackground:NO]; | |
319 [titleTextField_ setFont:[NSFont systemFontOfSize:15.0]]; | |
320 [titleTextField_ setStringValue: | |
321 base::SysUTF16ToNSString(autofillDialog->delegate()->DialogTitle())]; | |
322 [titleTextField_ sizeToFit]; | |
323 | |
324 accountChooser_.reset([[AutofillAccountChooser alloc] | |
325 initWithFrame:NSZeroRect | |
326 delegate:autofillDialog->delegate()]); | |
327 | |
328 loadingShieldController_.reset( | |
329 [[AutofillLoadingShieldController alloc] | |
330 initWithDelegate:autofillDialog->delegate()]); | |
331 [[loadingShieldController_ view] setHidden:YES]; | |
332 | |
333 overlayController_.reset( | |
334 [[AutofillOverlayController alloc] initWithDelegate: | |
335 autofillDialog->delegate()]); | |
336 [[overlayController_ view] setHidden:YES]; | |
337 | |
338 // This needs a flipped content view because otherwise the size | |
339 // animation looks odd. However, replacing the contentView for constrained | |
340 // windows does not work - it does custom rendering. | |
341 base::scoped_nsobject<NSView> flippedContentView( | |
342 [[FlippedView alloc] initWithFrame: | |
343 [[[self window] contentView] frame]]); | |
344 [flippedContentView setSubviews: | |
345 @[accountChooser_, | |
346 titleTextField_, | |
347 [mainContainer_ view], | |
348 [signInContainer_ view], | |
349 [loadingShieldController_ view], | |
350 [overlayController_ view]]]; | |
351 [flippedContentView setAutoresizingMask: | |
352 (NSViewWidthSizable | NSViewHeightSizable)]; | |
353 [[[self window] contentView] addSubview:flippedContentView]; | |
354 [mainContainer_ setAnchorView:[[accountChooser_ subviews] objectAtIndex:1]]; | |
355 } | |
356 return self; | |
357 } | |
358 | |
359 - (void)dealloc { | |
360 [[NSNotificationCenter defaultCenter] removeObserver:self]; | |
361 [super dealloc]; | |
362 } | |
363 | |
364 - (CGFloat)maxHeight { | |
365 NSRect dialogFrameRect = [[self window] frame]; | |
366 NSRect browserFrameRect = | |
367 [webContents_->GetView()->GetTopLevelNativeWindow() frame]; | |
368 dialogFrameRect.size.height = | |
369 NSMaxY(dialogFrameRect) - NSMinY(browserFrameRect); | |
370 dialogFrameRect = [[self window] contentRectForFrameRect:dialogFrameRect]; | |
371 return NSHeight(dialogFrameRect); | |
372 } | |
373 | |
374 - (void)updateSignInSizeConstraints { | |
375 // Adjust for the size of all decorations and paddings outside main content. | |
376 CGFloat minHeight = kMinimumContentsHeight - kDecorationHeight; | |
377 CGFloat maxHeight = std::max([self maxHeight] - kDecorationHeight, minHeight); | |
378 CGFloat width = NSWidth([[[self window] contentView] frame]); | |
379 | |
380 [signInContainer_ constrainSizeToMinimum:NSMakeSize(width, minHeight) | |
381 maximum:NSMakeSize(width, maxHeight)]; | |
382 } | |
383 | |
384 - (void)onContentViewFrameDidChange:(NSNotification*)notification { | |
385 [self updateSignInSizeConstraints]; | |
386 if ([[signInContainer_ view] isHidden]) | |
387 [self requestRelayout]; | |
388 } | |
389 | |
390 - (void)cancelRelayout { | |
391 [NSObject cancelPreviousPerformRequestsWithTarget:self | |
392 selector:@selector(performLayout) | |
393 object:nil]; | |
394 } | |
395 | |
396 - (void)requestRelayout { | |
397 [self cancelRelayout]; | |
398 [self performSelector:@selector(performLayout) withObject:nil afterDelay:0.0]; | |
399 } | |
400 | |
401 - (NSSize)preferredSize { | |
402 NSSize size; | |
403 | |
404 // Overall size is determined by either main container or sign in view. | |
405 if ([[signInContainer_ view] isHidden]) | |
406 size = [mainContainer_ preferredSize]; | |
407 else | |
408 size = [signInContainer_ preferredSize]; | |
409 | |
410 // Always make room for the header. | |
411 size.height += kDecorationHeight; | |
412 | |
413 if (![[overlayController_ view] isHidden]) { | |
414 CGFloat height = [overlayController_ heightForWidth:size.width]; | |
415 if (height != 0.0) | |
416 size.height = height; | |
417 } | |
418 | |
419 // Show as much of the main view as is possible without going past the | |
420 // bottom of the browser window. | |
421 size.height = std::min(size.height, [self maxHeight]); | |
422 | |
423 return size; | |
424 } | |
425 | |
426 - (void)performLayout { | |
427 NSRect contentRect = NSZeroRect; | |
428 contentRect.size = [self preferredSize]; | |
429 NSRect clientRect = contentRect; | |
430 clientRect.origin.y = chrome_style::kTitleTopPadding; | |
431 clientRect.size.height -= chrome_style::kTitleTopPadding + | |
432 chrome_style::kClientBottomPadding; | |
433 | |
434 [titleTextField_ setStringValue: | |
435 base::SysUTF16ToNSString(autofillDialog_->delegate()->DialogTitle())]; | |
436 [titleTextField_ sizeToFit]; | |
437 | |
438 NSRect headerRect, mainRect, titleRect, dummyRect; | |
439 NSDivideRect(clientRect, &headerRect, &mainRect, | |
440 kAccountChooserHeight, NSMinYEdge); | |
441 NSDivideRect(mainRect, &dummyRect, &mainRect, | |
442 autofill::kDetailVerticalPadding, NSMinYEdge); | |
443 headerRect = NSInsetRect(headerRect, chrome_style::kHorizontalPadding, 0); | |
444 NSDivideRect(headerRect, &titleRect, &headerRect, | |
445 NSWidth([titleTextField_ frame]), NSMinXEdge); | |
446 | |
447 // Align baseline of title with bottom of accountChooser. | |
448 base::scoped_nsobject<NSLayoutManager> layout_manager( | |
449 [[NSLayoutManager alloc] init]); | |
450 NSFont* titleFont = [titleTextField_ font]; | |
451 titleRect.origin.y += NSHeight(titleRect) - | |
452 [layout_manager defaultBaselineOffsetForFont:titleFont]; | |
453 [titleTextField_ setFrame:titleRect]; | |
454 | |
455 [accountChooser_ setFrame:headerRect]; | |
456 [accountChooser_ performLayout]; | |
457 if ([[signInContainer_ view] isHidden]) { | |
458 [[mainContainer_ view] setFrame:mainRect]; | |
459 [mainContainer_ performLayout]; | |
460 } else { | |
461 [[signInContainer_ view] setFrame:mainRect]; | |
462 } | |
463 | |
464 [[loadingShieldController_ view] setFrame:contentRect]; | |
465 [loadingShieldController_ performLayout]; | |
466 | |
467 [[overlayController_ view] setFrame:contentRect]; | |
468 [overlayController_ performLayout]; | |
469 | |
470 NSRect frameRect = [[self window] frameRectForContentRect:contentRect]; | |
471 [[self window] setFrame:frameRect display:YES]; | |
472 [[self window] recalculateKeyViewLoop]; | |
473 } | |
474 | |
475 - (IBAction)accept:(id)sender { | |
476 if ([mainContainer_ validate]) | |
477 autofillDialog_->delegate()->OnAccept(); | |
478 else | |
479 [mainContainer_ makeFirstInvalidInputFirstResponder]; | |
480 } | |
481 | |
482 - (IBAction)cancel:(id)sender { | |
483 autofillDialog_->delegate()->OnCancel(); | |
484 autofillDialog_->PerformClose(); | |
485 } | |
486 | |
487 - (void)show { | |
488 // Resizing the browser causes the ConstrainedWindow to move. | |
489 // Observe that to allow resizes based on browser size. | |
490 // NOTE: This MUST come last after all initial setup is done, because there | |
491 // is an immediate notification post registration. | |
492 DCHECK([self window]); | |
493 [[NSNotificationCenter defaultCenter] | |
494 addObserver:self | |
495 selector:@selector(onContentViewFrameDidChange:) | |
496 name:NSWindowDidMoveNotification | |
497 object:[self window]]; | |
498 | |
499 [self updateAccountChooser]; | |
500 [self updateNotificationArea]; | |
501 [self requestRelayout]; | |
502 } | |
503 | |
504 - (void)hide { | |
505 autofillDialog_->delegate()->OnCancel(); | |
506 autofillDialog_->PerformClose(); | |
507 } | |
508 | |
509 - (void)updateNotificationArea { | |
510 [mainContainer_ updateNotificationArea]; | |
511 } | |
512 | |
513 - (void)updateAccountChooser { | |
514 [accountChooser_ update]; | |
515 [mainContainer_ updateLegalDocuments]; | |
516 | |
517 // For the duration of the loading shield, hide the main contents. | |
518 // This prevents the currently focused text field "shining through". | |
519 // No need to remember previous state, because the loading shield | |
520 // always flows through to the main container. | |
521 [loadingShieldController_ update]; | |
522 [[mainContainer_ view] setHidden:![[loadingShieldController_ view] isHidden]]; | |
523 } | |
524 | |
525 - (void)updateButtonStrip { | |
526 [overlayController_ updateState]; | |
527 } | |
528 | |
529 - (void)updateSection:(autofill::DialogSection)section { | |
530 [[mainContainer_ sectionForId:section] update]; | |
531 [mainContainer_ updateSaveInChrome]; | |
532 } | |
533 | |
534 - (void)fillSection:(autofill::DialogSection)section | |
535 forInput:(const autofill::DetailInput&)input { | |
536 [[mainContainer_ sectionForId:section] fillForInput:input]; | |
537 [mainContainer_ updateSaveInChrome]; | |
538 } | |
539 | |
540 - (content::NavigationController*)showSignIn { | |
541 [self updateSignInSizeConstraints]; | |
542 [signInContainer_ loadSignInPage]; | |
543 [[mainContainer_ view] setHidden:YES]; | |
544 [[signInContainer_ view] setHidden:NO]; | |
545 [self requestRelayout]; | |
546 | |
547 return [signInContainer_ navigationController]; | |
548 } | |
549 | |
550 - (void)getInputs:(autofill::DetailOutputMap*)output | |
551 forSection:(autofill::DialogSection)section { | |
552 [[mainContainer_ sectionForId:section] getInputs:output]; | |
553 } | |
554 | |
555 - (NSString*)getCvc { | |
556 autofill::DialogSection section = autofill::SECTION_CC; | |
557 NSString* value = [[mainContainer_ sectionForId:section] suggestionText]; | |
558 if (!value) { | |
559 section = autofill::SECTION_CC_BILLING; | |
560 value = [[mainContainer_ sectionForId:section] suggestionText]; | |
561 } | |
562 return value; | |
563 } | |
564 | |
565 - (BOOL)saveDetailsLocally { | |
566 return [mainContainer_ saveDetailsLocally]; | |
567 } | |
568 | |
569 - (void)hideSignIn { | |
570 [[signInContainer_ view] setHidden:YES]; | |
571 [[mainContainer_ view] setHidden:NO]; | |
572 [self requestRelayout]; | |
573 } | |
574 | |
575 - (void)modelChanged { | |
576 [mainContainer_ modelChanged]; | |
577 } | |
578 | |
579 - (void)updateErrorBubble { | |
580 [mainContainer_ updateErrorBubble]; | |
581 } | |
582 | |
583 - (void)onSignInResize:(NSSize)size { | |
584 [signInContainer_ setPreferredSize:size]; | |
585 [self requestRelayout]; | |
586 } | |
587 | |
588 @end | |
589 | |
590 | |
591 @implementation AutofillDialogWindowController (TestableAutofillDialogView) | |
592 | |
593 - (void)setTextContents:(NSString*)text | |
594 forInput:(const autofill::DetailInput&)input { | |
595 for (size_t i = autofill::SECTION_MIN; i <= autofill::SECTION_MAX; ++i) { | |
596 autofill::DialogSection section = static_cast<autofill::DialogSection>(i); | |
597 // TODO(groby): Need to find the section for an input directly - wasteful. | |
598 [[mainContainer_ sectionForId:section] setFieldValue:text forInput:input]; | |
599 } | |
600 } | |
601 | |
602 - (void)setTextContents:(NSString*)text | |
603 ofSuggestionForSection:(autofill::DialogSection)section { | |
604 [[mainContainer_ sectionForId:section] setSuggestionFieldValue:text]; | |
605 } | |
606 | |
607 - (void)activateFieldForInput:(const autofill::DetailInput&)input { | |
608 for (size_t i = autofill::SECTION_MIN; i <= autofill::SECTION_MAX; ++i) { | |
609 autofill::DialogSection section = static_cast<autofill::DialogSection>(i); | |
610 [[mainContainer_ sectionForId:section] activateFieldForInput:input]; | |
611 } | |
612 } | |
613 | |
614 - (content::WebContents*)getSignInWebContents { | |
615 return [signInContainer_ webContents]; | |
616 } | |
617 | |
618 - (BOOL)IsShowingOverlay { | |
619 return ![[overlayController_ view] isHidden]; | |
620 } | |
621 | |
622 @end | |
OLD | NEW |