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