Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / chrome / browser / ui / cocoa / autofill / card_unmask_prompt_view_bridge.mm
blobc8a666bbdf7bbdb7f6b5dad963cf6fa233eb2850
1 // Copyright (c) 2015 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 #include "base/bind.h"
6 #include "base/message_loop/message_loop.h"
7 #include "base/strings/sys_string_conversions.h"
8 #include "chrome/browser/themes/theme_properties.h"
9 #include "chrome/browser/ui/autofill/autofill_dialog_models.h"
10 #include "chrome/browser/ui/autofill/autofill_dialog_types.h"
11 #include "chrome/browser/ui/chrome_style.h"
12 #import "chrome/browser/ui/cocoa/autofill/autofill_pop_up_button.h"
13 #import "chrome/browser/ui/cocoa/autofill/autofill_textfield.h"
14 #import "chrome/browser/ui/cocoa/autofill/autofill_tooltip_controller.h"
15 #include "chrome/browser/ui/cocoa/autofill/card_unmask_prompt_view_bridge.h"
16 #import "chrome/browser/ui/cocoa/constrained_window/constrained_window_button.h"
17 #import "chrome/browser/ui/cocoa/constrained_window/constrained_window_control_utils.h"
18 #import "chrome/browser/ui/cocoa/constrained_window/constrained_window_custom_sheet.h"
19 #import "chrome/browser/ui/cocoa/constrained_window/constrained_window_custom_window.h"
20 #import "chrome/browser/ui/cocoa/key_equivalent_constants.h"
21 #import "chrome/browser/ui/cocoa/l10n_util.h"
22 #import "chrome/browser/ui/cocoa/spinner_view.h"
23 #include "chrome/browser/ui/cocoa/themed_window.h"
24 #include "components/autofill/core/browser/ui/card_unmask_prompt_controller.h"
25 #include "content/public/browser/web_contents.h"
26 #include "grit/generated_resources.h"
27 #include "grit/theme_resources.h"
28 #include "skia/ext/skia_utils_mac.h"
29 #import "ui/base/cocoa/controls/hyperlink_button_cell.h"
30 #include "ui/base/cocoa/window_size_constants.h"
31 #include "ui/base/l10n/l10n_util.h"
32 #include "ui/base/theme_provider.h"
34 namespace {
36 const CGFloat kButtonGap = 6.0f;
37 const CGFloat kButtonsToRetriableErrorGap = 12.0f;
38 const CGFloat kCvcInputWidth = 64.0f;
39 const CGFloat kCvcInputToImageGap = 8.0f;
40 const CGFloat kDialogContentMinWidth = 210.0f;
41 const CGFloat kInputRowToInstructionsGap = 16.0f;
42 const CGFloat kInstructionsToTitleGap = 8.0f;
43 const CGFloat kPermanentErrorExteriorPadding = 12.0f;
44 const CGFloat kPermanentErrorHorizontalPadding = 16.0f;
45 const CGFloat kPermanentErrorVerticalPadding = 12.0f;
46 const CGFloat kProgressToInstructionsGap = 24.0f;
47 const CGFloat kRetriableErrorToInputRowGap = 4.0f;
48 const CGFloat kSeparatorHeight = 1.0f;
49 const CGFloat kSpinnerSize = 16.0f;
50 const CGFloat kSpinnerToProgressTextGap = 8.0f;
51 const CGFloat kYearToCvcGap = 12.0f;
53 const SkColor kPermanentErrorTextColor = SK_ColorWHITE;
54 // TODO(bondd): Unify colors with Views version and AutofillMessageView.
55 const SkColor kShadingColor = SkColorSetRGB(0xf2, 0xf2, 0xf2);
56 const SkColor kSubtleBorderColor = SkColorSetRGB(0xdf, 0xdf, 0xdf);
58 }  // namespace
60 namespace autofill {
62 CardUnmaskPromptView* CreateCardUnmaskPromptView(
63     CardUnmaskPromptController* controller,
64     content::WebContents* web_contents) {
65   return new CardUnmaskPromptViewBridge(controller, web_contents);
68 #pragma mark CardUnmaskPromptViewBridge
70 CardUnmaskPromptViewBridge::CardUnmaskPromptViewBridge(
71     CardUnmaskPromptController* controller,
72     content::WebContents* web_contents)
73     : controller_(controller),
74       web_contents_(web_contents),
75       weak_ptr_factory_(this) {
76   view_controller_.reset(
77       [[CardUnmaskPromptViewCocoa alloc] initWithBridge:this]);
80 CardUnmaskPromptViewBridge::~CardUnmaskPromptViewBridge() {
83 void CardUnmaskPromptViewBridge::Show() {
84   // Setup the constrained window that will show the view.
85   base::scoped_nsobject<NSWindow> window([[ConstrainedWindowCustomWindow alloc]
86       initWithContentRect:[[view_controller_ view] bounds]]);
87   [window setContentView:[view_controller_ view]];
88   base::scoped_nsobject<CustomConstrainedWindowSheet> sheet(
89       [[CustomConstrainedWindowSheet alloc] initWithCustomWindow:window]);
90   constrained_window_.reset(
91       new ConstrainedWindowMac(this, web_contents_, sheet));
94 void CardUnmaskPromptViewBridge::ControllerGone() {
95   controller_ = nullptr;
96   PerformClose();
99 void CardUnmaskPromptViewBridge::DisableAndWaitForVerification() {
100   [view_controller_ setProgressOverlayText:
101                         l10n_util::GetStringUTF16(
102                             IDS_AUTOFILL_CARD_UNMASK_VERIFICATION_IN_PROGRESS)
103                                showSpinner:YES];
106 void CardUnmaskPromptViewBridge::GotVerificationResult(
107     const base::string16& error_message,
108     bool allow_retry) {
109   if (error_message.empty()) {
110     [view_controller_ setProgressOverlayText:
111                           l10n_util::GetStringUTF16(
112                               IDS_AUTOFILL_CARD_UNMASK_VERIFICATION_SUCCESS)
113                                  showSpinner:NO];
115     base::MessageLoop::current()->PostDelayedTask(
116         FROM_HERE, base::Bind(&CardUnmaskPromptViewBridge::PerformClose,
117                               weak_ptr_factory_.GetWeakPtr()),
118         base::TimeDelta::FromSeconds(1));
119   } else {
120     [view_controller_ setProgressOverlayText:base::string16() showSpinner:NO];
122     if (allow_retry) {
123       // TODO(bondd): Views version never hides |errorLabel_|. When Views
124       // decides when to hide it then do the same thing here.
125       [view_controller_ setRetriableErrorMessage:error_message];
126     } else {
127       [view_controller_ setPermanentErrorMessage:error_message];
128     }
129   }
132 void CardUnmaskPromptViewBridge::OnConstrainedWindowClosed(
133     ConstrainedWindowMac* window) {
134   if (controller_)
135     controller_->OnUnmaskDialogClosed();
136   base::MessageLoop::current()->DeleteSoon(FROM_HERE, this);
139 CardUnmaskPromptController* CardUnmaskPromptViewBridge::GetController() {
140   return controller_;
143 content::WebContents* CardUnmaskPromptViewBridge::GetWebContents() {
144   return web_contents_;
147 void CardUnmaskPromptViewBridge::PerformClose() {
148   constrained_window_->CloseWebContentsModalDialog();
151 }  // autofill
153 #pragma mark CardUnmaskPromptViewCocoa
155 @implementation CardUnmaskPromptViewCocoa {
156   base::scoped_nsobject<NSBox> permanentErrorBox_;
157   base::scoped_nsobject<NSView> expirationView_;
158   base::scoped_nsobject<NSView> inputRowView_;
159   base::scoped_nsobject<NSView> progressOverlayView_;
160   base::scoped_nsobject<NSView> storageView_;
162   base::scoped_nsobject<NSTextField> titleLabel_;
163   base::scoped_nsobject<NSTextField> permanentErrorLabel_;
164   base::scoped_nsobject<NSTextField> instructionsLabel_;
165   base::scoped_nsobject<AutofillTextField> cvcInput_;
166   base::scoped_nsobject<AutofillPopUpButton> monthPopup_;
167   base::scoped_nsobject<AutofillPopUpButton> yearPopup_;
168   base::scoped_nsobject<NSImageView> cvcImageView_;
169   base::scoped_nsobject<NSButton> newCardButton_;
170   base::scoped_nsobject<NSButton> cancelButton_;
171   base::scoped_nsobject<NSButton> verifyButton_;
172   base::scoped_nsobject<NSButton> storageCheckbox_;
173   base::scoped_nsobject<AutofillTooltipController> storageTooltip_;
174   base::scoped_nsobject<NSTextField> errorLabel_;
175   base::scoped_nsobject<NSTextField> progressOverlayLabel_;
176   base::scoped_nsobject<SpinnerView> progressOverlaySpinner_;
178   int monthPopupDefaultIndex_;
179   int yearPopupDefaultIndex_;
181   // Owns |self|.
182   autofill::CardUnmaskPromptViewBridge* bridge_;
185 + (AutofillPopUpButton*)buildDatePopupWithModel:(ui::ComboboxModel&)model {
186   AutofillPopUpButton* popup =
187       [[AutofillPopUpButton alloc] initWithFrame:NSZeroRect pullsDown:NO];
189   for (int i = 0; i < model.GetItemCount(); ++i) {
190     [popup addItemWithTitle:base::SysUTF16ToNSString(model.GetItemAt(i))];
191   }
192   [popup sizeToFit];
193   return popup;
196 + (base::scoped_nsobject<NSBox>)createPlainBox {
197   base::scoped_nsobject<NSBox> box([[NSBox alloc] initWithFrame:NSZeroRect]);
198   [box setBoxType:NSBoxCustom];
199   [box setBorderType:NSNoBorder];
200   [box setTitlePosition:NSNoTitle];
201   return box;
204 // Set |view|'s frame to the minimum dimensions required to contain all of its
205 // subviews.
206 + (void)sizeToFitView:(NSView*)view {
207   NSRect frame = NSZeroRect;
208   for (NSView* child in [view subviews]) {
209     frame = NSUnionRect(frame, [child frame]);
210   }
211   [view setFrame:frame];
214 + (void)verticallyCenterSubviewsInView:(NSView*)view {
215   CGFloat height = NSHeight([view frame]);
216   for (NSView* child in [view subviews]) {
217     [child setFrameOrigin:NSMakePoint(
218                               NSMinX([child frame]),
219                               ceil((height - NSHeight([child frame])) * 0.5))];
220   }
223 - (id)initWithBridge:(autofill::CardUnmaskPromptViewBridge*)bridge {
224   DCHECK(bridge);
226   if ((self = [super initWithNibName:nil bundle:nil]))
227     bridge_ = bridge;
229   return self;
232 - (void)updateProgressOverlayOrigin {
233   // Center |progressOverlayView_| horizontally in the dialog, and position it
234   // a fixed distance below |instructionsLabel_|.
235   CGFloat viewMinY = NSMinY([instructionsLabel_ frame]) -
236                      kProgressToInstructionsGap -
237                      NSHeight([progressOverlayView_ frame]);
238   [progressOverlayView_
239       setFrameOrigin:NSMakePoint(
240                          NSMidX([[self view] frame]) -
241                              NSWidth([progressOverlayView_ frame]) / 2.0,
242                          viewMinY)];
245 - (void)setProgressOverlayText:(const base::string16&)text
246                    showSpinner:(BOOL)showSpinner {
247   if (!text.empty()) {
248     NSAttributedString* attributedString =
249         constrained_window::GetAttributedLabelString(
250             SysUTF16ToNSString(text), chrome_style::kTextFontStyle,
251             NSNaturalTextAlignment, NSLineBreakByWordWrapping);
252     [progressOverlayLabel_ setAttributedStringValue:attributedString];
253     [progressOverlayLabel_ sizeToFit];
254     CGFloat labelMinX = showSpinner
255                             ? NSMaxX([progressOverlaySpinner_ frame]) +
256                                   kSpinnerToProgressTextGap
257                             : 0;
258     [progressOverlayLabel_ setFrameOrigin:NSMakePoint(labelMinX, 0)];
260     [CardUnmaskPromptViewCocoa sizeToFitView:progressOverlayView_];
261     [self updateProgressOverlayOrigin];
262   }
264   [progressOverlayView_ setHidden:text.empty()];
265   [progressOverlaySpinner_ setHidden:!showSpinner];
266   [inputRowView_ setHidden:!text.empty()];
267   [errorLabel_ setHidden:!text.empty()];
268   [storageCheckbox_ setHidden:!text.empty()];
269   [[storageTooltip_ view] setHidden:!text.empty()];
270   [self updateVerifyButtonEnabled];
273 - (void)setInputsEnabled:(BOOL)enabled {
274   [cvcInput_ setEnabled:enabled];
275   [monthPopup_ setEnabled:enabled];
276   [yearPopup_ setEnabled:enabled];
277   [newCardButton_ setEnabled:enabled];
278   [storageCheckbox_ setEnabled:enabled];
281 - (void)setRetriableErrorMessage:(const base::string16&)text {
282   NSAttributedString* attributedString =
283       constrained_window::GetAttributedLabelString(
284           SysUTF16ToNSString(text), chrome_style::kTextFontStyle,
285           NSNaturalTextAlignment, NSLineBreakByWordWrapping);
286   [errorLabel_ setAttributedStringValue:attributedString];
288   // If there is more than one input showing, don't mark anything as invalid
289   // since we don't know the location of the problem.
290   if (!text.empty() &&
291       !bridge_->GetController()->ShouldRequestExpirationDate()) {
292     [cvcInput_ setValidityMessage:@"invalid"];
294     // Show "New card?" button, which when clicked will cause this dialog to
295     // ask for expiration date.
296     [self createNewCardButton];
297     [inputRowView_ addSubview:newCardButton_];
298   }
300   [self performLayoutAndDisplay:YES];
303 - (void)setPermanentErrorMessage:(const base::string16&)text {
304   if (!text.empty()) {
305     if (!permanentErrorBox_) {
306       permanentErrorBox_ = [CardUnmaskPromptViewCocoa createPlainBox];
307       [permanentErrorBox_ setFillColor:gfx::SkColorToCalibratedNSColor(
308                                            autofill::kWarningColor)];
309       [permanentErrorBox_
310           setContentViewMargins:NSMakeSize(kPermanentErrorHorizontalPadding,
311                                            kPermanentErrorVerticalPadding)];
313       permanentErrorLabel_.reset([constrained_window::CreateLabel() retain]);
314       [permanentErrorLabel_ setAutoresizingMask:NSViewWidthSizable];
315       [permanentErrorLabel_ setTextColor:gfx::SkColorToCalibratedNSColor(
316                                              kPermanentErrorTextColor)];
318       [permanentErrorBox_ addSubview:permanentErrorLabel_];
319       [[self view] addSubview:permanentErrorBox_];
320     }
322     NSAttributedString* attributedString =
323         constrained_window::GetAttributedLabelString(
324             SysUTF16ToNSString(text), chrome_style::kTextFontStyle,
325             NSNaturalTextAlignment, NSLineBreakByWordWrapping);
326     [permanentErrorLabel_ setAttributedStringValue:attributedString];
327   }
329   [permanentErrorBox_ setHidden:text.empty()];
330   [self setInputsEnabled:NO];
331   [self updateVerifyButtonEnabled];
332   [self setRetriableErrorMessage:base::string16()];
335 - (void)updateVerifyButtonEnabled {
336   BOOL enable = ![inputRowView_ isHidden] &&
337                 ![[permanentErrorLabel_ stringValue] length] &&
338                 bridge_->GetController()->InputCvcIsValid(
339                     base::SysNSStringToUTF16([cvcInput_ stringValue])) &&
340                 [self expirationDateIsValid];
342   [verifyButton_ setEnabled:enable];
345 - (void)onVerify:(id)sender {
346   bridge_->GetController()->OnUnmaskResponse(
347       base::SysNSStringToUTF16([cvcInput_ stringValue]),
348       base::SysNSStringToUTF16([monthPopup_ titleOfSelectedItem]),
349       base::SysNSStringToUTF16([yearPopup_ titleOfSelectedItem]),
350       [storageCheckbox_ state] == NSOnState);
353 - (void)onCancel:(id)sender {
354   bridge_->PerformClose();
357 - (void)onNewCard:(id)sender {
358   autofill::CardUnmaskPromptController* controller = bridge_->GetController();
359   controller->NewCardLinkClicked();
361   // |newCardButton_| will never be shown again for this dialog.
362   [newCardButton_ removeFromSuperview];
363   newCardButton_.reset();
365   [self createExpirationView];
366   [inputRowView_ addSubview:expirationView_];
367   [cvcInput_ setStringValue:@""];
368   [cvcInput_ setValidityMessage:@""];
369   [self setRetriableErrorMessage:base::string16()];
370   [self updateInstructionsText];
371   [self updateVerifyButtonEnabled];
372   [self performLayoutAndDisplay:YES];
375 - (BOOL)expirationDateIsValid {
376   if (!bridge_->GetController()->ShouldRequestExpirationDate())
377     return true;
379   return bridge_->GetController()->InputExpirationIsValid(
380       base::SysNSStringToUTF16([monthPopup_ titleOfSelectedItem]),
381       base::SysNSStringToUTF16([yearPopup_ titleOfSelectedItem]));
384 - (void)onExpirationDateChanged:(id)sender {
385   if ([self expirationDateIsValid]) {
386     if ([monthPopup_ invalid]) {
387       [monthPopup_ setValidityMessage:@""];
388       [yearPopup_ setValidityMessage:@""];
389       [self setRetriableErrorMessage:base::string16()];
390     }
391   } else if ([monthPopup_ indexOfSelectedItem] != monthPopupDefaultIndex_ &&
392              [yearPopup_ indexOfSelectedItem] != yearPopupDefaultIndex_) {
393     [monthPopup_ setValidityMessage:@"invalid"];
394     [yearPopup_ setValidityMessage:@"invalid"];
395     [self setRetriableErrorMessage:
396               l10n_util::GetStringUTF16(
397                   IDS_AUTOFILL_CARD_UNMASK_INVALID_EXPIRATION_DATE)];
398   }
400   [self updateVerifyButtonEnabled];
403 // Called when text in CVC input field changes.
404 - (void)controlTextDidChange:(NSNotification*)notification {
405   if (bridge_->GetController()->InputCvcIsValid(
406           base::SysNSStringToUTF16([cvcInput_ stringValue])))
407     [cvcInput_ setValidityMessage:@""];
409   [self updateVerifyButtonEnabled];
412 - (void)updateInstructionsText {
413   NSAttributedString* instructionsString =
414       constrained_window::GetAttributedLabelString(
415           SysUTF16ToNSString(
416               bridge_->GetController()->GetInstructionsMessage()),
417           chrome_style::kTextFontStyle, NSNaturalTextAlignment,
418           NSLineBreakByWordWrapping);
419   [instructionsLabel_ setAttributedStringValue:instructionsString];
422 - (void)createNewCardButton {
423   DCHECK(!newCardButton_);
424   newCardButton_.reset([[HyperlinkButtonCell
425       buttonWithString:l10n_util::GetNSString(
426                            IDS_AUTOFILL_CARD_UNMASK_NEW_CARD_LINK)] retain]);
427   [[newCardButton_ cell] setShouldUnderline:NO];
428   [newCardButton_ setTarget:self];
429   [newCardButton_ setAction:@selector(onNewCard:)];
430   [newCardButton_ sizeToFit];
433 - (void)createExpirationView {
434   DCHECK(!expirationView_);
435   expirationView_.reset([[NSView alloc] initWithFrame:NSZeroRect]);
437   // Add expiration month.
438   autofill::MonthComboboxModel monthModel;
439   monthPopupDefaultIndex_ = monthModel.GetDefaultIndex();
440   monthPopup_.reset(
441       [CardUnmaskPromptViewCocoa buildDatePopupWithModel:monthModel]);
442   [monthPopup_ setTarget:self];
443   [monthPopup_ setAction:@selector(onExpirationDateChanged:)];
444   [expirationView_ addSubview:monthPopup_];
446   // Add separator between month and year.
447   base::scoped_nsobject<NSTextField> separatorLabel(
448       [constrained_window::CreateLabel() retain]);
449   NSAttributedString* separatorString =
450       constrained_window::GetAttributedLabelString(
451           SysUTF16ToNSString(l10n_util::GetStringUTF16(
452               IDS_AUTOFILL_CARD_UNMASK_EXPIRATION_DATE_SEPARATOR)),
453           chrome_style::kTextFontStyle, NSNaturalTextAlignment,
454           NSLineBreakByWordWrapping);
455   [separatorLabel setAttributedStringValue:separatorString];
456   [separatorLabel sizeToFit];
457   [expirationView_ addSubview:separatorLabel];
459   // Add expiration year.
460   autofill::YearComboboxModel yearModel;
461   yearPopupDefaultIndex_ = yearModel.GetDefaultIndex();
462   yearPopup_.reset(
463       [CardUnmaskPromptViewCocoa buildDatePopupWithModel:yearModel]);
464   [yearPopup_ setTarget:self];
465   [yearPopup_ setAction:@selector(onExpirationDateChanged:)];
466   [expirationView_ addSubview:yearPopup_];
468   // Lay out month, separator, and year within |expirationView_|.
469   [separatorLabel setFrameOrigin:NSMakePoint(NSMaxX([monthPopup_ frame]), 0)];
470   [yearPopup_ setFrameOrigin:NSMakePoint(NSMaxX([separatorLabel frame]), 0)];
471   NSRect expirationFrame = NSUnionRect([monthPopup_ frame], [yearPopup_ frame]);
472   expirationFrame.size.width += kYearToCvcGap;
473   [expirationView_ setFrame:expirationFrame];
474   [CardUnmaskPromptViewCocoa verticallyCenterSubviewsInView:expirationView_];
477 - (void)createStorageViewWithController:
478         (autofill::CardUnmaskPromptController*)controller {
479   DCHECK(!storageView_);
480   storageView_.reset([[NSView alloc] initWithFrame:NSZeroRect]);
481   [storageView_ setAutoresizingMask:NSViewWidthSizable];
483   base::scoped_nsobject<NSBox> box = [CardUnmaskPromptViewCocoa createPlainBox];
484   [box setAutoresizingMask:NSViewWidthSizable];
485   [box setFillColor:gfx::SkColorToCalibratedNSColor(kShadingColor)];
486   [box setContentViewMargins:NSMakeSize(chrome_style::kHorizontalPadding,
487                                         chrome_style::kClientBottomPadding)];
488   [storageView_ addSubview:box];
490   // Add "Store card on this device" checkbox.
491   storageCheckbox_.reset([[NSButton alloc] initWithFrame:NSZeroRect]);
492   [storageCheckbox_ setButtonType:NSSwitchButton];
493   [storageCheckbox_
494       setTitle:base::SysUTF16ToNSString(l10n_util::GetStringUTF16(
495                    IDS_AUTOFILL_CARD_UNMASK_PROMPT_STORAGE_CHECKBOX))];
496   [storageCheckbox_
497       setState:(controller->GetStoreLocallyStartState() ? NSOnState
498                                                         : NSOffState)];
499   [storageCheckbox_ sizeToFit];
500   [box addSubview:storageCheckbox_];
502   // Add "?" icon with tooltip.
503   storageTooltip_.reset([[AutofillTooltipController alloc]
504       initWithArrowLocation:info_bubble::kTopRight]);
505   [storageTooltip_ setImage:ui::ResourceBundle::GetSharedInstance()
506                                 .GetNativeImageNamed(IDR_AUTOFILL_TOOLTIP_ICON)
507                                 .ToNSImage()];
508   [storageTooltip_
509       setMessage:base::SysUTF16ToNSString(l10n_util::GetStringUTF16(
510                      IDS_AUTOFILL_CARD_UNMASK_PROMPT_STORAGE_TOOLTIP))];
511   [box addSubview:[storageTooltip_ view]];
512   [[storageTooltip_ view] setFrameOrigin:
513       NSMakePoint(NSMaxX([storageCheckbox_ frame]) + kButtonGap, 0)];
515   // Add horizontal separator.
516   base::scoped_nsobject<NSBox> separator =
517       [CardUnmaskPromptViewCocoa createPlainBox];
518   [separator setAutoresizingMask:NSViewWidthSizable];
519   [separator setFillColor:gfx::SkColorToCalibratedNSColor(kSubtleBorderColor)];
520   [storageView_ addSubview:separator];
522   [box sizeToFit];
523   [separator setFrame:NSMakeRect(0, NSMaxY([box frame]), NSWidth([box frame]),
524                                  kSeparatorHeight)];
525   [CardUnmaskPromptViewCocoa sizeToFitView:storageView_];
528 // +---------------------------------------------------------------------------+
529 // | titleLabel_        (Single line.)                                         |
530 // |---------------------------------------------------------------------------|
531 // | permanentErrorBox_ (Multiline, may be hidden.)                            |
532 // |---------------------------------------------------------------------------|
533 // | instructionsLabel_ (Multiline.)                                           |
534 // |---------------------------------------------------------------------------|
535 // | monthPopup_ yearPopup_ cvcInput_ cvcImageView_ newCardButton_             |
536 // |     (All enclosed in inputRowView_. month, year, and newCard may be       |
537 // |      hidden.)                                                             |
538 // |---------------------------------------------------------------------------|
539 // | errorLabel_ (Multiline. Always takes up space for one line even if empty. |
540 // |              Hidden when progressOverlayView_ is displayed.)              |
541 // |---------------------------------------------------------------------------|
542 // |                                                         [Cancel] [Verify] |
543 // |---------------------------------------------------------------------------|
544 // | separator (NSBox.)                                                        |
545 // | storageCheckbox_ storageTooltip_ (Both enclosed in another NSBox.         |
546 // |                                   Checkbox and tooltip may be hidden.)    |
547 // |     (Both NSBoxes are enclosed in storageView_. Will all be nil if        |
548 // |      !CanStoreLocally()).                                                 |
549 // +---------------------------------------------------------------------------+
551 // progressOverlayView_:
552 //     (Displayed below instructionsLabel_, may be hidden.
553 //      progressOverlaySpinner_ may be hidden while progressOverlayLabel_
554 //      is shown.)
555 // +---------------------------------------------------------------------------+
556 // | progressOverlaySpinner_ progressOverlayLabel_                             |
557 // +---------------------------------------------------------------------------+
558 - (void)performLayoutAndDisplay:(BOOL)display {
559   // Lay out |inputRowView_| contents.
560   // |expirationView_| may be nil, which will result in |cvcInput_| getting an
561   // x value of 0.
562   [cvcInput_ setFrame:NSMakeRect(NSMaxX([expirationView_ frame]), 0,
563                                  kCvcInputWidth, NSHeight([cvcInput_ frame]))];
564   [cvcImageView_
565       setFrameOrigin:NSMakePoint(
566                          NSMaxX([cvcInput_ frame]) + kCvcInputToImageGap, 0)];
567   [newCardButton_
568       setFrameOrigin:NSMakePoint(
569                          NSMaxX([cvcImageView_ frame]) + kButtonGap, 0)];
570   [CardUnmaskPromptViewCocoa sizeToFitView:inputRowView_];
571   [CardUnmaskPromptViewCocoa verticallyCenterSubviewsInView:inputRowView_];
573   // Calculate dialog content width.
574   CGFloat contentWidth =
575       std::max(NSWidth([titleLabel_ frame]), NSWidth([inputRowView_ frame]));
576   contentWidth = std::max(contentWidth,
577                           NSWidth(NSUnionRect([storageCheckbox_ frame],
578                                               [[storageTooltip_ view] frame])));
579   contentWidth = std::max(contentWidth, kDialogContentMinWidth);
581   CGFloat contentMinX = chrome_style::kHorizontalPadding;
582   CGFloat contentMaxX = contentMinX + contentWidth;
583   CGFloat dialogWidth = contentMaxX + chrome_style::kHorizontalPadding;
585   CGFloat verifyMinY =
586       storageView_ ? NSMaxY([storageView_ frame]) + chrome_style::kRowPadding
587                    : chrome_style::kClientBottomPadding;
588   [verifyButton_
589       setFrameOrigin:NSMakePoint(contentMaxX - NSWidth([verifyButton_ frame]),
590                                  verifyMinY)];
592   [cancelButton_
593       setFrameOrigin:NSMakePoint(NSMinX([verifyButton_ frame]) - kButtonGap -
594                                      NSWidth([cancelButton_ frame]),
595                                  NSMinY([verifyButton_ frame]))];
597   [errorLabel_ setFrame:NSMakeRect(contentMinX, NSMaxY([cancelButton_ frame]) +
598                                                     kButtonsToRetriableErrorGap,
599                                    contentWidth, 0)];
600   cocoa_l10n_util::WrapOrSizeToFit(errorLabel_);
602   [inputRowView_ setFrameOrigin:NSMakePoint(contentMinX,
603                                             NSMaxY([errorLabel_ frame]) +
604                                                 kRetriableErrorToInputRowGap)];
606   [instructionsLabel_
607       setFrame:NSMakeRect(contentMinX, NSMaxY([inputRowView_ frame]) +
608                                            kInputRowToInstructionsGap,
609                           contentWidth, 0)];
610   cocoa_l10n_util::WrapOrSizeToFit(instructionsLabel_);
612   // Lay out permanent error box.
613   CGFloat titleMinY;
614   if (permanentErrorBox_ && ![permanentErrorBox_ isHidden]) {
615     [permanentErrorBox_
616         setFrame:NSMakeRect(0, NSMaxY([instructionsLabel_ frame]) +
617                                    kPermanentErrorExteriorPadding,
618                             dialogWidth, 0)];
619     cocoa_l10n_util::WrapOrSizeToFit(permanentErrorLabel_);
620     [permanentErrorBox_ sizeToFit];
621     titleMinY =
622         NSMaxY([permanentErrorBox_ frame]) + kPermanentErrorExteriorPadding;
623   } else {
624     titleMinY = NSMaxY([instructionsLabel_ frame]) + kInstructionsToTitleGap;
625   }
627   [titleLabel_ setFrameOrigin:NSMakePoint(contentMinX, titleMinY)];
629   // Set dialog size.
630   [[self view]
631       setFrameSize:NSMakeSize(dialogWidth, NSMaxY([titleLabel_ frame]) +
632                                                chrome_style::kTitleTopPadding)];
634   [self updateProgressOverlayOrigin];
636   NSRect frameRect =
637       [[[self view] window] frameRectForContentRect:[[self view] frame]];
638   [[[self view] window] setFrame:frameRect display:display];
641 - (void)loadView {
642   autofill::CardUnmaskPromptController* controller = bridge_->GetController();
643   DCHECK(controller);
645   base::scoped_nsobject<NSView> mainView(
646       [[NSView alloc] initWithFrame:NSZeroRect]);
648   inputRowView_.reset([[NSView alloc] initWithFrame:NSZeroRect]);
649   [mainView addSubview:inputRowView_];
651   if (controller->CanStoreLocally()) {
652     [self createStorageViewWithController:controller];
653     [mainView addSubview:storageView_];
654   }
656   // Add progress overlay.
657   progressOverlayView_.reset([[NSView alloc] initWithFrame:NSZeroRect]);
658   [progressOverlayView_ setHidden:YES];
659   [mainView addSubview:progressOverlayView_];
661   progressOverlayLabel_.reset([constrained_window::CreateLabel() retain]);
662   NSView* webContentView = bridge_->GetWebContents()->GetNativeView();
663   ui::ThemeProvider* themeProvider = [[webContentView window] themeProvider];
664   DCHECK(themeProvider);
665   NSColor* throbberBlueColor = themeProvider->GetNSColor(
666       ThemeProperties::COLOR_THROBBER_SPINNING);
667   [progressOverlayLabel_ setTextColor:throbberBlueColor];
668   [progressOverlayView_ addSubview:progressOverlayLabel_];
670   progressOverlaySpinner_.reset([[SpinnerView alloc]
671       initWithFrame:NSMakeRect(0, 0, kSpinnerSize, kSpinnerSize)]);
672   [progressOverlayView_ addSubview:progressOverlaySpinner_];
674   // Add title label.
675   titleLabel_.reset([constrained_window::CreateLabel() retain]);
676   NSAttributedString* titleString =
677       constrained_window::GetAttributedLabelString(
678           SysUTF16ToNSString(controller->GetWindowTitle()),
679           chrome_style::kTitleFontStyle, NSNaturalTextAlignment,
680           NSLineBreakByWordWrapping);
681   [titleLabel_ setAttributedStringValue:titleString];
682   [titleLabel_ sizeToFit];
683   [mainView addSubview:titleLabel_];
685   // Add instructions label.
686   instructionsLabel_.reset([constrained_window::CreateLabel() retain]);
687   [self updateInstructionsText];
688   [mainView addSubview:instructionsLabel_];
690   // Add expiration date.
691   if (controller->ShouldRequestExpirationDate()) {
692     [self createExpirationView];
693     [inputRowView_ addSubview:expirationView_];
694   }
696   // Add CVC text input.
697   cvcInput_.reset([[AutofillTextField alloc] initWithFrame:NSZeroRect]);
698   [[cvcInput_ cell]
699       setPlaceholderString:l10n_util::GetNSString(
700                                IDS_AUTOFILL_DIALOG_PLACEHOLDER_CVC)];
701   [[cvcInput_ cell] setScrollable:YES];
702   [cvcInput_ setDelegate:self];
703   [cvcInput_ sizeToFit];
704   [inputRowView_ addSubview:cvcInput_];
706   // Add CVC image.
707   ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
708   NSImage* cvcImage =
709       rb.GetNativeImageNamed(controller->GetCvcImageRid()).ToNSImage();
710   cvcImageView_.reset([[NSImageView alloc] initWithFrame:NSZeroRect]);
711   [cvcImageView_ setImage:cvcImage];
712   [cvcImageView_ setFrameSize:[cvcImage size]];
713   [inputRowView_ addSubview:cvcImageView_];
715   // Add error message label.
716   errorLabel_.reset([constrained_window::CreateLabel() retain]);
717   [errorLabel_
718       setTextColor:gfx::SkColorToCalibratedNSColor(autofill::kWarningColor)];
719   [mainView addSubview:errorLabel_];
721   // Add cancel button.
722   cancelButton_.reset(
723       [[ConstrainedWindowButton alloc] initWithFrame:NSZeroRect]);
724   [cancelButton_ setTitle:l10n_util::GetNSStringWithFixup(IDS_CANCEL)];
725   [cancelButton_ setKeyEquivalent:kKeyEquivalentEscape];
726   [cancelButton_ setTarget:self];
727   [cancelButton_ setAction:@selector(onCancel:)];
728   [cancelButton_ sizeToFit];
729   [mainView addSubview:cancelButton_];
731   // Add verify button.
732   verifyButton_.reset(
733       [[ConstrainedWindowButton alloc] initWithFrame:NSZeroRect]);
734   [verifyButton_ setTitle:l10n_util::GetNSStringWithFixup(
735                               IDS_AUTOFILL_CARD_UNMASK_CONFIRM_BUTTON)];
736   [verifyButton_ setKeyEquivalent:kKeyEquivalentReturn];
737   [verifyButton_ setTarget:self];
738   [verifyButton_ setAction:@selector(onVerify:)];
739   [verifyButton_ sizeToFit];
740   [self updateVerifyButtonEnabled];
741   [mainView addSubview:verifyButton_];
743   [self setView:mainView];
744   [self performLayoutAndDisplay:NO];
747 @end