MacViews: Use Mac's "Constrained Window Button" style for Button::STYLE_BUTTON LabelB...
[chromium-blink-merge.git] / chrome / browser / ui / views / autofill / card_unmask_prompt_views.cc
blobf7e28324d071b764d26e6f757519eeae7a263072
1 // Copyright 2014 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 "chrome/browser/ui/views/autofill/card_unmask_prompt_views.h"
7 #include "base/basictypes.h"
8 #include "base/location.h"
9 #include "base/single_thread_task_runner.h"
10 #include "base/strings/utf_string_conversions.h"
11 #include "base/thread_task_runner_handle.h"
12 #include "chrome/browser/ui/autofill/autofill_dialog_types.h"
13 #include "chrome/browser/ui/autofill/create_card_unmask_prompt_view.h"
14 #include "chrome/browser/ui/views/autofill/decorated_textfield.h"
15 #include "chrome/browser/ui/views/autofill/tooltip_icon.h"
16 #include "chrome/grit/generated_resources.h"
17 #include "components/autofill/core/browser/ui/card_unmask_prompt_controller.h"
18 #include "components/constrained_window/constrained_window_views.h"
19 #include "components/web_modal/web_contents_modal_dialog_host.h"
20 #include "components/web_modal/web_contents_modal_dialog_manager.h"
21 #include "components/web_modal/web_contents_modal_dialog_manager_delegate.h"
22 #include "grit/theme_resources.h"
23 #include "third_party/skia/include/core/SkColor.h"
24 #include "ui/base/l10n/l10n_util.h"
25 #include "ui/base/resource/resource_bundle.h"
26 #include "ui/compositor/compositing_recorder.h"
27 #include "ui/gfx/canvas.h"
28 #include "ui/gfx/geometry/safe_integer_conversions.h"
29 #include "ui/views/background.h"
30 #include "ui/views/controls/button/checkbox.h"
31 #include "ui/views/controls/combobox/combobox.h"
32 #include "ui/views/controls/image_view.h"
33 #include "ui/views/controls/label.h"
34 #include "ui/views/controls/link.h"
35 #include "ui/views/controls/throbber.h"
36 #include "ui/views/layout/box_layout.h"
37 #include "ui/views/widget/widget.h"
38 #include "ui/views/window/dialog_client_view.h"
40 namespace autofill {
42 // The number of pixels of blank space on the outer horizontal edges of the
43 // dialog.
44 const int kEdgePadding = 19;
46 SkColor kGreyTextColor = SkColorSetRGB(0x64, 0x64, 0x64);
48 CardUnmaskPromptView* CreateCardUnmaskPromptView(
49 CardUnmaskPromptController* controller,
50 content::WebContents* web_contents) {
51 return new CardUnmaskPromptViews(controller, web_contents);
54 CardUnmaskPromptViews::CardUnmaskPromptViews(
55 CardUnmaskPromptController* controller,
56 content::WebContents* web_contents)
57 : controller_(controller),
58 web_contents_(web_contents),
59 main_contents_(nullptr),
60 instructions_(nullptr),
61 permanent_error_label_(nullptr),
62 input_row_(nullptr),
63 cvc_input_(nullptr),
64 month_input_(nullptr),
65 year_input_(nullptr),
66 new_card_link_(nullptr),
67 error_icon_(nullptr),
68 error_label_(nullptr),
69 storage_row_(nullptr),
70 storage_checkbox_(nullptr),
71 progress_overlay_(nullptr),
72 progress_throbber_(nullptr),
73 progress_label_(nullptr),
74 overlay_animation_(this),
75 weak_ptr_factory_(this) {
78 CardUnmaskPromptViews::~CardUnmaskPromptViews() {
79 if (controller_)
80 controller_->OnUnmaskDialogClosed();
83 void CardUnmaskPromptViews::Show() {
84 constrained_window::ShowWebModalDialogViews(this, web_contents_);
87 void CardUnmaskPromptViews::ControllerGone() {
88 controller_ = nullptr;
89 ClosePrompt();
92 void CardUnmaskPromptViews::DisableAndWaitForVerification() {
93 SetInputsEnabled(false);
94 progress_overlay_->SetAlpha(0);
95 progress_overlay_->SetVisible(true);
96 progress_throbber_->Start();
97 overlay_animation_.Show();
98 GetDialogClientView()->UpdateDialogButtons();
99 Layout();
102 void CardUnmaskPromptViews::GotVerificationResult(
103 const base::string16& error_message,
104 bool allow_retry) {
105 progress_throbber_->Stop();
106 if (error_message.empty()) {
107 progress_label_->SetText(l10n_util::GetStringUTF16(
108 IDS_AUTOFILL_CARD_UNMASK_VERIFICATION_SUCCESS));
109 progress_throbber_->SetChecked(true);
110 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
111 FROM_HERE, base::Bind(&CardUnmaskPromptViews::ClosePrompt,
112 weak_ptr_factory_.GetWeakPtr()),
113 controller_->GetSuccessMessageDuration());
114 } else {
115 // TODO(estade): it's somewhat jarring when the error comes back too
116 // quickly.
117 overlay_animation_.Reset();
118 if (storage_row_)
119 storage_row_->SetAlpha(255);
120 progress_overlay_->SetVisible(false);
122 if (allow_retry) {
123 SetInputsEnabled(true);
125 if (!controller_->ShouldRequestExpirationDate()) {
126 // If there is more than one input showing, don't mark anything as
127 // invalid since we don't know the location of the problem.
128 cvc_input_->SetInvalid(true);
130 // Show a "New card?" link, which when clicked will cause us to ask
131 // for expiration date.
132 ShowNewCardLink();
135 // TODO(estade): When do we hide |error_label_|?
136 SetRetriableErrorMessage(error_message);
137 } else {
138 permanent_error_label_->SetText(error_message);
139 permanent_error_label_->SetVisible(true);
140 SetRetriableErrorMessage(base::string16());
142 GetDialogClientView()->UpdateDialogButtons();
145 Layout();
148 void CardUnmaskPromptViews::LinkClicked(views::Link* source, int event_flags) {
149 DCHECK_EQ(source, new_card_link_);
150 controller_->NewCardLinkClicked();
151 for (int i = 0; i < input_row_->child_count(); ++i)
152 input_row_->child_at(i)->SetVisible(true);
154 new_card_link_->SetVisible(false);
155 input_row_->InvalidateLayout();
156 cvc_input_->SetInvalid(false);
157 cvc_input_->SetText(base::string16());
158 GetDialogClientView()->UpdateDialogButtons();
159 GetWidget()->UpdateWindowTitle();
160 instructions_->SetText(controller_->GetInstructionsMessage());
161 SetRetriableErrorMessage(base::string16());
164 void CardUnmaskPromptViews::SetRetriableErrorMessage(
165 const base::string16& message) {
166 if (message.empty()) {
167 error_label_->SetMultiLine(false);
168 error_label_->SetText(base::ASCIIToUTF16(" "));
169 error_icon_->SetVisible(false);
170 } else {
171 error_label_->SetMultiLine(true);
172 error_label_->SetText(message);
173 error_icon_->SetVisible(true);
176 // Update the dialog's size.
177 if (GetWidget() && web_contents_) {
178 constrained_window::UpdateWebContentsModalDialogPosition(
179 GetWidget(),
180 web_modal::WebContentsModalDialogManager::FromWebContents(web_contents_)
181 ->delegate()
182 ->GetWebContentsModalDialogHost());
185 Layout();
188 void CardUnmaskPromptViews::SetInputsEnabled(bool enabled) {
189 cvc_input_->SetEnabled(enabled);
190 if (storage_checkbox_)
191 storage_checkbox_->SetEnabled(enabled);
192 month_input_->SetEnabled(enabled);
193 year_input_->SetEnabled(enabled);
196 void CardUnmaskPromptViews::ShowNewCardLink() {
197 if (new_card_link_)
198 return;
200 new_card_link_ = new views::Link(
201 l10n_util::GetStringUTF16(IDS_AUTOFILL_CARD_UNMASK_NEW_CARD_LINK));
202 new_card_link_->SetBorder(views::Border::CreateEmptyBorder(0, 7, 0, 0));
203 new_card_link_->SetUnderline(false);
204 new_card_link_->set_listener(this);
205 input_row_->AddChildView(new_card_link_);
208 views::View* CardUnmaskPromptViews::GetContentsView() {
209 InitIfNecessary();
210 return this;
213 views::View* CardUnmaskPromptViews::CreateFootnoteView() {
214 if (!controller_->CanStoreLocally())
215 return nullptr;
217 // Local storage checkbox and (?) tooltip.
218 storage_row_ = new FadeOutView();
219 views::BoxLayout* storage_row_layout = new views::BoxLayout(
220 views::BoxLayout::kHorizontal, kEdgePadding, kEdgePadding, 0);
221 storage_row_->SetLayoutManager(storage_row_layout);
222 storage_row_->SetBorder(
223 views::Border::CreateSolidSidedBorder(1, 0, 0, 0, kSubtleBorderColor));
224 storage_row_->set_background(
225 views::Background::CreateSolidBackground(kLightShadingColor));
227 storage_checkbox_ = new views::Checkbox(l10n_util::GetStringUTF16(
228 IDS_AUTOFILL_CARD_UNMASK_PROMPT_STORAGE_CHECKBOX));
229 storage_checkbox_->SetChecked(controller_->GetStoreLocallyStartState());
230 storage_row_->AddChildView(storage_checkbox_);
231 storage_row_layout->SetFlexForView(storage_checkbox_, 1);
233 storage_row_->AddChildView(new TooltipIcon(l10n_util::GetStringUTF16(
234 IDS_AUTOFILL_CARD_UNMASK_PROMPT_STORAGE_TOOLTIP)));
236 return storage_row_;
239 gfx::Size CardUnmaskPromptViews::GetPreferredSize() const {
240 // Must hardcode a width so the label knows where to wrap.
241 const int kWidth = 375;
242 return gfx::Size(kWidth, GetHeightForWidth(kWidth));
245 void CardUnmaskPromptViews::Layout() {
246 gfx::Rect contents_bounds = GetContentsBounds();
247 main_contents_->SetBoundsRect(contents_bounds);
249 // The progress overlay extends from the top of the input row
250 // to the bottom of the content area.
251 gfx::RectF input_rect = input_row_->GetContentsBounds();
252 View::ConvertRectToTarget(input_row_, this, &input_rect);
253 input_rect.set_height(contents_bounds.height());
254 contents_bounds.Intersect(gfx::ToNearestRect(input_rect));
255 progress_overlay_->SetBoundsRect(contents_bounds);
258 int CardUnmaskPromptViews::GetHeightForWidth(int width) const {
259 if (!has_children())
260 return 0;
261 const gfx::Insets insets = GetInsets();
262 return main_contents_->GetHeightForWidth(width - insets.width()) +
263 insets.height();
266 void CardUnmaskPromptViews::OnNativeThemeChanged(const ui::NativeTheme* theme) {
267 SkColor bg_color =
268 theme->GetSystemColor(ui::NativeTheme::kColorId_DialogBackground);
269 progress_overlay_->set_background(
270 views::Background::CreateSolidBackground(bg_color));
271 progress_label_->SetBackgroundColor(bg_color);
272 progress_label_->SetEnabledColor(theme->GetSystemColor(
273 ui::NativeTheme::kColorId_ThrobberSpinningColor));
276 ui::ModalType CardUnmaskPromptViews::GetModalType() const {
277 return ui::MODAL_TYPE_CHILD;
280 base::string16 CardUnmaskPromptViews::GetWindowTitle() const {
281 return controller_->GetWindowTitle();
284 void CardUnmaskPromptViews::DeleteDelegate() {
285 delete this;
288 int CardUnmaskPromptViews::GetDialogButtons() const {
289 return ui::DIALOG_BUTTON_OK | ui::DIALOG_BUTTON_CANCEL;
292 base::string16 CardUnmaskPromptViews::GetDialogButtonLabel(
293 ui::DialogButton button) const {
294 if (button == ui::DIALOG_BUTTON_OK)
295 return l10n_util::GetStringUTF16(IDS_AUTOFILL_CARD_UNMASK_CONFIRM_BUTTON);
297 return DialogDelegateView::GetDialogButtonLabel(button);
300 bool CardUnmaskPromptViews::ShouldDefaultButtonBeBlue() const {
301 return true;
304 bool CardUnmaskPromptViews::IsDialogButtonEnabled(
305 ui::DialogButton button) const {
306 if (button == ui::DIALOG_BUTTON_CANCEL)
307 return true;
309 DCHECK_EQ(ui::DIALOG_BUTTON_OK, button);
311 return cvc_input_->enabled() &&
312 controller_->InputCvcIsValid(cvc_input_->text()) &&
313 ExpirationDateIsValid();
316 views::View* CardUnmaskPromptViews::GetInitiallyFocusedView() {
317 return cvc_input_;
320 bool CardUnmaskPromptViews::Cancel() {
321 return true;
324 bool CardUnmaskPromptViews::Accept() {
325 if (!controller_)
326 return true;
328 controller_->OnUnmaskResponse(
329 cvc_input_->text(),
330 month_input_->visible()
331 ? month_input_->GetTextForRow(month_input_->selected_index())
332 : base::string16(),
333 year_input_->visible()
334 ? year_input_->GetTextForRow(year_input_->selected_index())
335 : base::string16(),
336 storage_checkbox_ ? storage_checkbox_->checked() : false);
337 return false;
340 void CardUnmaskPromptViews::ContentsChanged(
341 views::Textfield* sender,
342 const base::string16& new_contents) {
343 if (controller_->InputCvcIsValid(new_contents))
344 cvc_input_->SetInvalid(false);
346 GetDialogClientView()->UpdateDialogButtons();
349 void CardUnmaskPromptViews::OnPerformAction(views::Combobox* combobox) {
350 if (ExpirationDateIsValid()) {
351 if (month_input_->invalid()) {
352 month_input_->SetInvalid(false);
353 year_input_->SetInvalid(false);
354 SetRetriableErrorMessage(base::string16());
356 } else if (month_input_->selected_index() !=
357 month_combobox_model_.GetDefaultIndex() &&
358 year_input_->selected_index() !=
359 year_combobox_model_.GetDefaultIndex()) {
360 month_input_->SetInvalid(true);
361 year_input_->SetInvalid(true);
362 SetRetriableErrorMessage(l10n_util::GetStringUTF16(
363 IDS_AUTOFILL_CARD_UNMASK_INVALID_EXPIRATION_DATE));
366 GetDialogClientView()->UpdateDialogButtons();
369 void CardUnmaskPromptViews::AnimationProgressed(
370 const gfx::Animation* animation) {
371 uint8_t alpha = static_cast<uint8_t>(animation->CurrentValueBetween(0, 255));
372 progress_overlay_->SetAlpha(alpha);
373 if (storage_row_)
374 storage_row_->SetAlpha(255 - alpha);
377 void CardUnmaskPromptViews::InitIfNecessary() {
378 if (has_children())
379 return;
381 main_contents_ = new views::View();
382 main_contents_->SetLayoutManager(
383 new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 12));
384 AddChildView(main_contents_);
386 permanent_error_label_ = new views::Label();
387 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
388 permanent_error_label_->SetFontList(
389 rb.GetFontList(ui::ResourceBundle::BoldFont));
390 permanent_error_label_->set_background(
391 views::Background::CreateSolidBackground(kWarningColor));
392 permanent_error_label_->SetBorder(
393 views::Border::CreateEmptyBorder(12, kEdgePadding, 12, kEdgePadding));
394 permanent_error_label_->SetEnabledColor(SK_ColorWHITE);
395 permanent_error_label_->SetAutoColorReadabilityEnabled(false);
396 permanent_error_label_->SetVisible(false);
397 permanent_error_label_->SetMultiLine(true);
398 permanent_error_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
399 main_contents_->AddChildView(permanent_error_label_);
401 views::View* controls_container = new views::View();
402 controls_container->SetLayoutManager(
403 new views::BoxLayout(views::BoxLayout::kVertical, kEdgePadding, 0, 0));
404 main_contents_->AddChildView(controls_container);
406 instructions_ = new views::Label(controller_->GetInstructionsMessage());
407 instructions_->SetEnabledColor(kGreyTextColor);
408 instructions_->SetMultiLine(true);
409 instructions_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
410 instructions_->SetBorder(views::Border::CreateEmptyBorder(0, 0, 16, 0));
411 controls_container->AddChildView(instructions_);
413 input_row_ = new views::View();
414 input_row_->SetLayoutManager(
415 new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, 5));
416 controls_container->AddChildView(input_row_);
418 month_input_ = new views::Combobox(&month_combobox_model_);
419 month_input_->set_listener(this);
420 input_row_->AddChildView(month_input_);
421 views::Label* separator = new views::Label(l10n_util::GetStringUTF16(
422 IDS_AUTOFILL_CARD_UNMASK_EXPIRATION_DATE_SEPARATOR));
423 separator->SetEnabledColor(kGreyTextColor);
424 input_row_->AddChildView(separator);
425 year_input_ = new views::Combobox(&year_combobox_model_);
426 year_input_->set_listener(this);
427 input_row_->AddChildView(year_input_);
428 input_row_->AddChildView(new views::Label(base::ASCIIToUTF16(" ")));
429 // Hide all of the above as appropriate.
430 if (!controller_->ShouldRequestExpirationDate()) {
431 for (int i = 0; i < input_row_->child_count(); ++i)
432 input_row_->child_at(i)->SetVisible(false);
435 cvc_input_ = new DecoratedTextfield(
436 base::string16(),
437 l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_PLACEHOLDER_CVC), this);
438 cvc_input_->set_default_width_in_chars(8);
439 input_row_->AddChildView(cvc_input_);
441 views::ImageView* cvc_image = new views::ImageView();
442 cvc_image->SetImage(rb.GetImageSkiaNamed(controller_->GetCvcImageRid()));
443 input_row_->AddChildView(cvc_image);
445 views::View* temporary_error = new views::View();
446 views::BoxLayout* temporary_error_layout =
447 new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, 4);
448 temporary_error->SetLayoutManager(temporary_error_layout);
449 temporary_error_layout->set_cross_axis_alignment(
450 views::BoxLayout::CROSS_AXIS_ALIGNMENT_START);
451 temporary_error->SetBorder(views::Border::CreateEmptyBorder(8, 0, 0, 0));
452 controls_container->AddChildView(temporary_error);
454 error_icon_ = new views::ImageView();
455 error_icon_->SetVisible(false);
456 error_icon_->SetImage(ui::ResourceBundle::GetSharedInstance()
457 .GetImageNamed(IDR_ALERT_RED)
458 .ToImageSkia());
459 temporary_error->AddChildView(error_icon_);
461 // Reserve vertical space for the error label, assuming it's one line.
462 error_label_ = new views::Label(base::ASCIIToUTF16(" "));
463 error_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
464 error_label_->SetEnabledColor(kWarningColor);
465 temporary_error->AddChildView(error_label_);
466 temporary_error_layout->SetFlexForView(error_label_, 1);
468 progress_overlay_ = new FadeOutView();
469 progress_overlay_->set_fade_everything(true);
470 views::BoxLayout* progress_layout =
471 new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, 5);
472 progress_layout->set_cross_axis_alignment(
473 views::BoxLayout::CROSS_AXIS_ALIGNMENT_CENTER);
474 progress_layout->set_main_axis_alignment(
475 views::BoxLayout::MAIN_AXIS_ALIGNMENT_CENTER);
476 progress_overlay_->SetLayoutManager(progress_layout);
478 progress_overlay_->SetVisible(false);
479 AddChildView(progress_overlay_);
481 progress_throbber_ = new views::Throbber();
482 progress_overlay_->AddChildView(progress_throbber_);
484 progress_label_ = new views::Label(l10n_util::GetStringUTF16(
485 IDS_AUTOFILL_CARD_UNMASK_VERIFICATION_IN_PROGRESS));
486 progress_overlay_->AddChildView(progress_label_);
489 bool CardUnmaskPromptViews::ExpirationDateIsValid() const {
490 if (!controller_->ShouldRequestExpirationDate())
491 return true;
493 return controller_->InputExpirationIsValid(
494 month_input_->GetTextForRow(month_input_->selected_index()),
495 year_input_->GetTextForRow(year_input_->selected_index()));
498 void CardUnmaskPromptViews::ClosePrompt() {
499 GetWidget()->Close();
502 CardUnmaskPromptViews::FadeOutView::FadeOutView()
503 : fade_everything_(false), alpha_(255) {
505 CardUnmaskPromptViews::FadeOutView::~FadeOutView() {
508 void CardUnmaskPromptViews::FadeOutView::PaintChildren(
509 const ui::PaintContext& context) {
510 ui::CompositingRecorder recorder(context, alpha_);
511 views::View::PaintChildren(context);
514 void CardUnmaskPromptViews::FadeOutView::OnPaint(gfx::Canvas* canvas) {
515 if (!fade_everything_ || alpha_ == 255)
516 return views::View::OnPaint(canvas);
518 canvas->SaveLayerAlpha(alpha_);
519 views::View::OnPaint(canvas);
520 canvas->Restore();
523 void CardUnmaskPromptViews::FadeOutView::SetAlpha(uint8_t alpha) {
524 alpha_ = alpha;
525 SchedulePaint();
528 } // namespace autofill