blob: 40db3e6d7cd780463088efd9170f38894a3856e3 [file] [log] [blame]
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome/browser/ui/views/autofill/autofill_dialog_views.h"
6
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00007#include <utility>
8
9#include "base/bind.h"
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +010010#include "base/strings/utf_string_conversions.h"
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000011#include "chrome/browser/profiles/profile.h"
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +010012#include "chrome/browser/ui/autofill/autofill_dialog_sign_in_delegate.h"
Ben Murdoch32409262013-08-07 11:04:47 +010013#include "chrome/browser/ui/autofill/autofill_dialog_view_delegate.h"
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +010014#include "chrome/browser/ui/views/autofill/decorated_textfield.h"
Torne (Richard Coles)58218062012-11-14 11:43:16 +000015#include "chrome/browser/ui/views/constrained_window_views.h"
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +010016#include "components/autofill/content/browser/wallet/wallet_service_url.h"
Ben Murdocheb525c52013-07-10 11:40:50 +010017#include "components/autofill/core/browser/autofill_type.h"
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +010018#include "components/web_modal/web_contents_modal_dialog_manager.h"
19#include "components/web_modal/web_contents_modal_dialog_manager_delegate.h"
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000020#include "content/public/browser/native_web_keyboard_event.h"
21#include "content/public/browser/navigation_controller.h"
22#include "content/public/browser/web_contents.h"
23#include "content/public/browser/web_contents_view.h"
24#include "grit/theme_resources.h"
25#include "grit/ui_resources.h"
26#include "third_party/skia/include/core/SkColor.h"
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +010027#include "ui/base/animation/multi_animation.h"
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000028#include "ui/base/l10n/l10n_util.h"
29#include "ui/base/models/combobox_model.h"
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010030#include "ui/base/models/menu_model.h"
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000031#include "ui/base/resource/resource_bundle.h"
32#include "ui/gfx/canvas.h"
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +010033#include "ui/gfx/path.h"
34#include "ui/gfx/skia_util.h"
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000035#include "ui/views/background.h"
Torne (Richard Coles)58218062012-11-14 11:43:16 +000036#include "ui/views/border.h"
Ben Murdoch58e6fbe2013-07-26 10:20:38 +010037#include "ui/views/bubble/bubble_border.h"
38#include "ui/views/bubble/bubble_frame_view.h"
Ben Murdochca12bfa2013-07-23 11:17:05 +010039#include "ui/views/controls/button/blue_button.h"
Torne (Richard Coles)58218062012-11-14 11:43:16 +000040#include "ui/views/controls/button/checkbox.h"
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000041#include "ui/views/controls/button/image_button.h"
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010042#include "ui/views/controls/button/label_button.h"
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +010043#include "ui/views/controls/button/label_button_border.h"
Torne (Richard Coles)58218062012-11-14 11:43:16 +000044#include "ui/views/controls/combobox/combobox.h"
Ben Murdocheb525c52013-07-10 11:40:50 +010045#include "ui/views/controls/focusable_border.h"
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000046#include "ui/views/controls/image_view.h"
Torne (Richard Coles)58218062012-11-14 11:43:16 +000047#include "ui/views/controls/label.h"
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000048#include "ui/views/controls/link.h"
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000049#include "ui/views/controls/menu/menu_runner.h"
Torne (Richard Coles)58218062012-11-14 11:43:16 +000050#include "ui/views/controls/separator.h"
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010051#include "ui/views/controls/styled_label.h"
Torne (Richard Coles)58218062012-11-14 11:43:16 +000052#include "ui/views/controls/textfield/textfield.h"
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000053#include "ui/views/controls/webview/webview.h"
Torne (Richard Coles)58218062012-11-14 11:43:16 +000054#include "ui/views/layout/box_layout.h"
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000055#include "ui/views/layout/fill_layout.h"
Torne (Richard Coles)58218062012-11-14 11:43:16 +000056#include "ui/views/layout/grid_layout.h"
57#include "ui/views/layout/layout_constants.h"
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000058#include "ui/views/widget/widget.h"
59#include "ui/views/window/dialog_client_view.h"
Torne (Richard Coles)58218062012-11-14 11:43:16 +000060
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +010061using web_modal::WebContentsModalDialogManager;
62
Torne (Richard Coles)58218062012-11-14 11:43:16 +000063namespace autofill {
64
65namespace {
66
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010067// The minimum useful height of the contents area of the dialog.
68const int kMinimumContentsHeight = 100;
69
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000070// Horizontal padding between text and other elements (in pixels).
71const int kAroundTextPadding = 4;
72
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010073// The space between the edges of a notification bar and the text within (in
74// pixels).
Ben Murdoch558790d2013-07-30 15:19:42 +010075const int kNotificationPadding = 17;
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010076
77// Vertical padding above and below each detail section (in pixels).
Ben Murdochbbcdd452013-07-25 10:06:34 +010078const int kDetailSectionVerticalPadding = 10;
79
Ben Murdoch7dbb3d52013-07-17 14:55:54 +010080const int kAutocheckoutStepsAreaPadding = 28;
81const int kAutocheckoutStepInset = 20;
Ben Murdocheb525c52013-07-10 11:40:50 +010082
Ben Murdoch7dbb3d52013-07-17 14:55:54 +010083const int kAutocheckoutProgressBarWidth = 375;
84const int kAutocheckoutProgressBarHeight = 15;
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000085
Ben Murdoch7dbb3d52013-07-17 14:55:54 +010086const int kArrowHeight = 7;
87const int kArrowWidth = 2 * kArrowHeight;
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000088
Ben Murdoch558790d2013-07-30 15:19:42 +010089// The padding inside the edges of the dialog, in pixels.
90const int kDialogEdgePadding = 20;
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010091
92// Slight shading for mouse hover and legal document background.
93SkColor kShadingColor = SkColorSetARGB(7, 0, 0, 0);
94
95// A border color for the legal document view.
96SkColor kSubtleBorderColor = SkColorSetARGB(10, 0, 0, 0);
97
Ben Murdochbbcdd452013-07-25 10:06:34 +010098// The top and bottom padding, in pixels, for the suggestions menu dropdown
99// arrows.
100const int kMenuButtonTopInset = 3;
101const int kMenuButtonBottomInset = 6;
Ben Murdoch7dbb3d52013-07-17 14:55:54 +0100102
Ben Murdoch7dbb3d52013-07-17 14:55:54 +0100103// Spacing between lines of text in the overlay view.
104const int kOverlayTextInterlineSpacing = 10;
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100105
Ben Murdoch558790d2013-07-30 15:19:42 +0100106// Spacing below image and above text messages in overlay view.
107const int kOverlayImageBottomMargin = 50;
108
Ben Murdochca12bfa2013-07-23 11:17:05 +0100109// A dimmer text color used in various parts of the dialog. TODO(estade): should
110// this be part of NativeTheme? Currently the value is duplicated in several
111// places.
112const SkColor kGreyTextColor = SkColorSetRGB(102, 102, 102);
113
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000114const char kNotificationAreaClassName[] = "autofill/NotificationArea";
Ben Murdoch7dbb3d52013-07-17 14:55:54 +0100115const char kOverlayViewClassName[] = "autofill/OverlayView";
116
117typedef ui::MultiAnimation::Part Part;
118typedef ui::MultiAnimation::Parts Parts;
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000119
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100120// Draws an arrow at the top of |canvas| pointing to |tip_x|.
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +0100121void DrawArrow(gfx::Canvas* canvas,
122 int tip_x,
123 const SkColor& fill_color,
124 const SkColor& stroke_color) {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100125 const int arrow_half_width = kArrowWidth / 2.0f;
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100126
127 SkPath arrow;
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +0100128 arrow.moveTo(tip_x - arrow_half_width, kArrowHeight);
129 arrow.lineTo(tip_x, 0);
130 arrow.lineTo(tip_x + arrow_half_width, kArrowHeight);
131
132 SkPaint fill_paint;
133 fill_paint.setColor(fill_color);
134 canvas->DrawPath(arrow, fill_paint);
135
136 if (stroke_color != SK_ColorTRANSPARENT) {
137 SkPaint stroke_paint;
138 stroke_paint.setColor(stroke_color);
139 stroke_paint.setStyle(SkPaint::kStroke_Style);
140 canvas->DrawPath(arrow, stroke_paint);
141 }
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100142}
143
144// This class handles layout for the first row of a SuggestionView.
145// It exists to circumvent shortcomings of GridLayout and BoxLayout (namely that
146// the former doesn't fully respect child visibility, and that the latter won't
147// expand a single child).
148class SectionRowView : public views::View {
149 public:
150 SectionRowView() {}
151 virtual ~SectionRowView() {}
152
153 // views::View implementation:
154 virtual gfx::Size GetPreferredSize() OVERRIDE {
155 // Only the height matters anyway.
156 return child_at(2)->GetPreferredSize();
157 }
158
159 virtual void Layout() OVERRIDE {
160 const gfx::Rect bounds = GetContentsBounds();
161
162 // Icon is left aligned.
163 int start_x = bounds.x();
164 views::View* icon = child_at(0);
165 if (icon->visible()) {
166 icon->SizeToPreferredSize();
167 icon->SetX(start_x);
168 icon->SetY(bounds.y() +
169 (bounds.height() - icon->bounds().height()) / 2);
170 start_x += icon->bounds().width() + kAroundTextPadding;
171 }
172
173 // Textfield is right aligned.
174 int end_x = bounds.width();
175 views::View* decorated = child_at(2);
176 if (decorated->visible()) {
177 decorated->SizeToPreferredSize();
178 decorated->SetX(bounds.width() - decorated->bounds().width());
179 decorated->SetY(bounds.y());
180 end_x = decorated->bounds().x() - kAroundTextPadding;
181 }
182
183 // Label takes up all the space in between.
184 views::View* label = child_at(1);
185 if (label->visible())
186 label->SetBounds(start_x, bounds.y(), end_x - start_x, bounds.height());
187
188 views::View::Layout();
189 }
190
191 private:
192 DISALLOW_COPY_AND_ASSIGN(SectionRowView);
193};
194
195// This view is used for the contents of the error bubble widget.
196class ErrorBubbleContents : public views::View {
197 public:
Ben Murdoch558790d2013-07-30 15:19:42 +0100198 explicit ErrorBubbleContents(const base::string16& message)
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +0100199 : color_(kWarningColor) {
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +0100200 set_border(views::Border::CreateEmptyBorder(kArrowHeight - 3, 0, 0, 0));
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100201
202 views::Label* label = new views::Label();
203 label->SetText(message);
204 label->SetAutoColorReadabilityEnabled(false);
205 label->SetEnabledColor(SK_ColorWHITE);
206 label->set_border(
207 views::Border::CreateSolidSidedBorder(5, 10, 5, 10, color_));
208 label->set_background(
209 views::Background::CreateSolidBackground(color_));
210 SetLayoutManager(new views::FillLayout());
211 AddChildView(label);
212 }
213 virtual ~ErrorBubbleContents() {}
214
215 virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE {
216 views::View::OnPaint(canvas);
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +0100217 DrawArrow(canvas, width() / 2.0f, color_, SK_ColorTRANSPARENT);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100218 }
219
220 private:
221 SkColor color_;
222
223 DISALLOW_COPY_AND_ASSIGN(ErrorBubbleContents);
224};
225
Torne (Richard Coles)a93a17c2013-05-15 11:34:50 +0100226// A view that runs a callback whenever its bounds change.
227class DetailsContainerView : public views::View {
228 public:
229 explicit DetailsContainerView(const base::Closure& callback)
230 : bounds_changed_callback_(callback) {}
231 virtual ~DetailsContainerView() {}
232
233 // views::View implementation.
234 virtual void OnBoundsChanged(const gfx::Rect& previous_bounds) OVERRIDE {
235 bounds_changed_callback_.Run();
236 }
237
238 private:
239 base::Closure bounds_changed_callback_;
240
241 DISALLOW_COPY_AND_ASSIGN(DetailsContainerView);
242};
243
Ben Murdocheb525c52013-07-10 11:40:50 +0100244// A view that propagates visibility and preferred size changes.
245class LayoutPropagationView : public views::View {
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +0100246 public:
Ben Murdocheb525c52013-07-10 11:40:50 +0100247 LayoutPropagationView() {}
248 virtual ~LayoutPropagationView() {}
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +0100249
250 protected:
251 virtual void ChildVisibilityChanged(views::View* child) OVERRIDE {
252 PreferredSizeChanged();
253 }
Ben Murdocheb525c52013-07-10 11:40:50 +0100254 virtual void ChildPreferredSizeChanged(views::View* child) OVERRIDE {
255 PreferredSizeChanged();
256 }
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +0100257
258 private:
Ben Murdocheb525c52013-07-10 11:40:50 +0100259 DISALLOW_COPY_AND_ASSIGN(LayoutPropagationView);
260};
261
262// A class which displays the status of an individual step in an
263// Autocheckout flow.
264class AutocheckoutStepProgressView : public views::View {
265 public:
Ben Murdoch558790d2013-07-30 15:19:42 +0100266 AutocheckoutStepProgressView(const base::string16& description,
Ben Murdocheb525c52013-07-10 11:40:50 +0100267 const gfx::Font& font,
268 const SkColor color,
269 const bool is_icon_visible) {
270 views::GridLayout* layout = new views::GridLayout(this);
271 SetLayoutManager(layout);
272 const int kColumnSetId = 0;
273 views::ColumnSet* columns = layout->AddColumnSet(kColumnSetId);
274 columns->AddColumn(views::GridLayout::LEADING,
Ben Murdoch7dbb3d52013-07-17 14:55:54 +0100275 views::GridLayout::CENTER,
Ben Murdocheb525c52013-07-10 11:40:50 +0100276 0,
277 views::GridLayout::USE_PREF,
278 0,
279 0);
280 columns->AddPaddingColumn(0, 8);
281 columns->AddColumn(views::GridLayout::LEADING,
Ben Murdoch7dbb3d52013-07-17 14:55:54 +0100282 views::GridLayout::CENTER,
Ben Murdocheb525c52013-07-10 11:40:50 +0100283 0,
284 views::GridLayout::USE_PREF,
285 0,
286 0);
287 layout->StartRow(0, kColumnSetId);
288 views::Label* label = new views::Label();
289 label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
290 label->set_border(views::Border::CreateEmptyBorder(0, 0, 0, 0));
291 label->SetText(description);
292 label->SetFont(font);
293 label->SetEnabledColor(color);
294
295 views::ImageView* icon = new views::ImageView();
296 icon->SetVisible(is_icon_visible);
297 icon->SetImage(ui::ResourceBundle::GetSharedInstance().GetImageNamed(
298 IDR_WALLET_STEP_CHECK).ToImageSkia());
299
300 layout->AddView(icon);
301 layout->AddView(label);
302 }
303
304 virtual ~AutocheckoutStepProgressView() {}
305
306 private:
307 DISALLOW_COPY_AND_ASSIGN(AutocheckoutStepProgressView);
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +0100308};
309
Ben Murdoch558790d2013-07-30 15:19:42 +0100310// A tooltip icon (just an ImageView with a tooltip). Looks like (?).
311class TooltipIcon : public views::ImageView {
312 public:
313 explicit TooltipIcon(const base::string16& tooltip) : tooltip_(tooltip) {
314 SetImage(ui::ResourceBundle::GetSharedInstance().GetImageNamed(
315 IDR_AUTOFILL_TOOLTIP_ICON).ToImageSkia());
316 }
317 virtual ~TooltipIcon() {}
318
319 // views::View implementation
320 virtual bool GetTooltipText(const gfx::Point& p,
321 base::string16* tooltip) const OVERRIDE {
322 *tooltip = tooltip_;
323 return !tooltip_.empty();
324 }
325
326 private:
327 base::string16 tooltip_;
328
329 DISALLOW_COPY_AND_ASSIGN(TooltipIcon);
330};
331
332// A View for a single notification banner.
333class NotificationView : public views::View {
334 public:
335 explicit NotificationView(const DialogNotification& data) : checkbox_(NULL) {
336 scoped_ptr<views::View> label_view;
337 if (data.HasCheckbox()) {
338 scoped_ptr<views::Checkbox> checkbox(
339 new views::Checkbox(base::string16()));
340 if (!data.interactive())
341 checkbox->SetState(views::Button::STATE_DISABLED);
342 checkbox->SetText(data.display_text());
343 checkbox->SetTextMultiLine(true);
344 checkbox->SetHorizontalAlignment(gfx::ALIGN_LEFT);
345 checkbox->SetTextColor(views::Button::STATE_NORMAL,
346 data.GetTextColor());
347 checkbox->SetTextColor(views::Button::STATE_HOVERED,
348 data.GetTextColor());
349 checkbox->SetChecked(data.checked());
350 checkbox_ = checkbox.get();
351 label_view.reset(checkbox.release());
352 } else {
353 scoped_ptr<views::Label> label(new views::Label());
354 label->SetText(data.display_text());
355 label->SetMultiLine(true);
356 label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
357 label->SetAutoColorReadabilityEnabled(false);
358 label->SetEnabledColor(data.GetTextColor());
359 label_view.reset(label.release());
360 }
361
362 AddChildView(label_view.release());
363
364 if (!data.tooltip_text().empty())
365 AddChildView(new TooltipIcon(data.tooltip_text()));
366
367 set_background(
368 views::Background::CreateSolidBackground(data.GetBackgroundColor()));
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +0100369 set_border(views::Border::CreateSolidSidedBorder(1, 0, 1, 0,
370 data.GetBorderColor()));
Ben Murdoch558790d2013-07-30 15:19:42 +0100371 }
372
373 virtual ~NotificationView() {}
374
375 views::Checkbox* checkbox() {
376 return checkbox_;
377 }
378
379 // views::View implementation
380 virtual gfx::Insets GetInsets() const OVERRIDE {
381 int vertical_padding = kNotificationPadding;
382 if (checkbox_)
383 vertical_padding -= 3;
384 return gfx::Insets(vertical_padding, kDialogEdgePadding,
385 vertical_padding, kDialogEdgePadding);
386 }
387
388 virtual int GetHeightForWidth(int width) OVERRIDE {
389 int label_width = width - GetInsets().width();
390 if (child_count() > 1) {
391 views::View* tooltip_icon = child_at(1);
392 label_width -= tooltip_icon->GetPreferredSize().width() +
393 kDialogEdgePadding;
394 }
395
396 return child_at(0)->GetHeightForWidth(label_width) + GetInsets().height();
397 }
398
399 virtual void Layout() OVERRIDE {
400 // Surprisingly, GetContentsBounds() doesn't consult GetInsets().
401 gfx::Rect bounds = GetLocalBounds();
402 bounds.Inset(GetInsets());
403 int right_bound = bounds.right();
404
405 if (child_count() > 1) {
406 // The icon takes up the entire vertical space and an extra 20px on
407 // each side. This increases the hover target for the tooltip.
408 views::View* tooltip_icon = child_at(1);
409 gfx::Size icon_size = tooltip_icon->GetPreferredSize();
410 int icon_width = icon_size.width() + kDialogEdgePadding;
411 right_bound -= icon_width;
412 tooltip_icon->SetBounds(
413 right_bound, 0,
414 icon_width + kDialogEdgePadding, GetLocalBounds().height());
415 }
416
417 child_at(0)->SetBounds(bounds.x(), bounds.y(),
418 right_bound - bounds.x(), bounds.height());
419 }
420
421 private:
422 // The checkbox associated with this notification, or NULL if there is none.
423 views::Checkbox* checkbox_;
424
425 DISALLOW_COPY_AND_ASSIGN(NotificationView);
426};
427
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000428} // namespace
429
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100430// AutofillDialogViews::ErrorBubble --------------------------------------------
431
432AutofillDialogViews::ErrorBubble::ErrorBubble(views::View* anchor,
Ben Murdoch558790d2013-07-30 15:19:42 +0100433 const base::string16& message)
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100434 : anchor_(anchor),
Torne (Richard Coles)a93a17c2013-05-15 11:34:50 +0100435 contents_(new ErrorBubbleContents(message)),
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100436 observer_(this) {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100437 widget_ = new views::Widget;
438 views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP);
Ben Murdocheb525c52013-07-10 11:40:50 +0100439 params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100440 views::Widget* anchor_widget = anchor->GetWidget();
Torne (Richard Coles)a93a17c2013-05-15 11:34:50 +0100441 DCHECK(anchor_widget);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100442 params.parent = anchor_widget->GetNativeView();
443
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100444 widget_->Init(params);
Torne (Richard Coles)a93a17c2013-05-15 11:34:50 +0100445 widget_->SetContentsView(contents_);
446 UpdatePosition();
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100447 observer_.Add(widget_);
448}
449
450AutofillDialogViews::ErrorBubble::~ErrorBubble() {
451 if (widget_)
Torne (Richard Coles)a93a17c2013-05-15 11:34:50 +0100452 widget_->Close();
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100453}
454
455bool AutofillDialogViews::ErrorBubble::IsShowing() {
456 return widget_ && widget_->IsVisible();
457}
458
Torne (Richard Coles)a93a17c2013-05-15 11:34:50 +0100459void AutofillDialogViews::ErrorBubble::UpdatePosition() {
460 if (!widget_)
461 return;
462
463 if (!anchor_->GetVisibleBounds().IsEmpty()) {
464 widget_->SetBounds(GetBoundsForWidget());
465 widget_->SetVisibilityChangedAnimationsEnabled(true);
466 widget_->Show();
467 } else {
468 widget_->SetVisibilityChangedAnimationsEnabled(false);
469 widget_->Hide();
470 }
471}
472
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100473void AutofillDialogViews::ErrorBubble::OnWidgetClosing(views::Widget* widget) {
474 DCHECK_EQ(widget_, widget);
475 observer_.Remove(widget_);
476 widget_ = NULL;
477}
478
Torne (Richard Coles)a93a17c2013-05-15 11:34:50 +0100479gfx::Rect AutofillDialogViews::ErrorBubble::GetBoundsForWidget() {
480 gfx::Rect anchor_bounds = anchor_->GetBoundsInScreen();
481 gfx::Rect bubble_bounds;
482 bubble_bounds.set_size(contents_->GetPreferredSize());
483 bubble_bounds.set_x(anchor_bounds.right() -
484 (anchor_bounds.width() + bubble_bounds.width()) / 2);
485 const int kErrorBubbleOverlap = 3;
486 bubble_bounds.set_y(anchor_bounds.bottom() - kErrorBubbleOverlap);
487
488 return bubble_bounds;
489}
490
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000491// AutofillDialogViews::AccountChooser -----------------------------------------
492
493AutofillDialogViews::AccountChooser::AccountChooser(
Ben Murdoch32409262013-08-07 11:04:47 +0100494 AutofillDialogViewDelegate* delegate)
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000495 : image_(new views::ImageView()),
496 label_(new views::Label()),
497 arrow_(new views::ImageView()),
Torne (Richard Coles)a93a17c2013-05-15 11:34:50 +0100498 link_(new views::Link()),
Ben Murdoch32409262013-08-07 11:04:47 +0100499 delegate_(delegate) {
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000500 SetLayoutManager(
501 new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0,
502 kAroundTextPadding));
503 AddChildView(image_);
504 AddChildView(label_);
505
506 arrow_->SetImage(ui::ResourceBundle::GetSharedInstance().GetImageNamed(
507 IDR_MENU_DROPARROW).ToImageSkia());
508 AddChildView(arrow_);
509
510 link_->set_listener(this);
511 AddChildView(link_);
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000512}
513
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000514AutofillDialogViews::AccountChooser::~AccountChooser() {}
515
516void AutofillDialogViews::AccountChooser::Update() {
Ben Murdoch32409262013-08-07 11:04:47 +0100517 SetVisible(!delegate_->ShouldShowSpinner());
Ben Murdocheb525c52013-07-10 11:40:50 +0100518
Ben Murdoch32409262013-08-07 11:04:47 +0100519 gfx::Image icon = delegate_->AccountChooserImage();
Torne (Richard Coles)a93a17c2013-05-15 11:34:50 +0100520 image_->SetImage(icon.AsImageSkia());
Ben Murdoch32409262013-08-07 11:04:47 +0100521 label_->SetText(delegate_->AccountChooserText());
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000522
Ben Murdoch32409262013-08-07 11:04:47 +0100523 bool show_link = !delegate_->MenuModelForAccountChooser();
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000524 label_->SetVisible(!show_link);
525 arrow_->SetVisible(!show_link);
Ben Murdoch32409262013-08-07 11:04:47 +0100526 link_->SetText(delegate_->SignInLinkText());
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000527 link_->SetVisible(show_link);
528
529 menu_runner_.reset();
530
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100531 PreferredSizeChanged();
532}
533
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000534bool AutofillDialogViews::AccountChooser::OnMousePressed(
535 const ui::MouseEvent& event) {
536 // Return true so we get the release event.
Ben Murdoch32409262013-08-07 11:04:47 +0100537 if (delegate_->MenuModelForAccountChooser())
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000538 return event.IsOnlyLeftMouseButton();
539
540 return false;
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000541}
542
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000543void AutofillDialogViews::AccountChooser::OnMouseReleased(
544 const ui::MouseEvent& event) {
545 if (!HitTestPoint(event.location()))
546 return;
547
Ben Murdoch32409262013-08-07 11:04:47 +0100548 ui::MenuModel* model = delegate_->MenuModelForAccountChooser();
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000549 if (!model)
550 return;
551
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100552 menu_runner_.reset(new views::MenuRunner(model));
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000553 ignore_result(
554 menu_runner_->RunMenuAt(GetWidget(),
555 NULL,
556 GetBoundsInScreen(),
557 views::MenuItemView::TOPRIGHT,
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +0100558 ui::MENU_SOURCE_MOUSE,
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000559 0));
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000560}
561
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000562void AutofillDialogViews::AccountChooser::LinkClicked(views::Link* source,
563 int event_flags) {
Ben Murdoch32409262013-08-07 11:04:47 +0100564 delegate_->SignInLinkClicked();
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000565}
566
Ben Murdoch7dbb3d52013-07-17 14:55:54 +0100567// AutofillDialogViews::OverlayView --------------------------------------------
568
569AutofillDialogViews::OverlayView::OverlayView(views::ButtonListener* listener)
570 : image_view_(new views::ImageView()),
571 message_stack_(new views::View()),
Ben Murdoch558790d2013-07-30 15:19:42 +0100572 button_(new views::BlueButton(listener, base::string16())) {
Ben Murdoch7dbb3d52013-07-17 14:55:54 +0100573 set_background(views::Background::CreateSolidBackground(GetNativeTheme()->
574 GetSystemColor(ui::NativeTheme::kColorId_DialogBackground)));
575
576 AddChildView(image_view_);
577
578 AddChildView(message_stack_);
579 message_stack_->SetLayoutManager(
580 new views::BoxLayout(views::BoxLayout::kVertical, 0, 0,
581 kOverlayTextInterlineSpacing));
582 message_stack_->set_border(views::Border::CreateEmptyBorder(
Ben Murdoch558790d2013-07-30 15:19:42 +0100583 kDialogEdgePadding, kDialogEdgePadding, 0, kDialogEdgePadding));
Ben Murdoch7dbb3d52013-07-17 14:55:54 +0100584
585 AddChildView(button_);
Ben Murdoch7dbb3d52013-07-17 14:55:54 +0100586 button_->set_focusable(true);
587}
588
589AutofillDialogViews::OverlayView::~OverlayView() {}
590
Ben Murdoch558790d2013-07-30 15:19:42 +0100591int AutofillDialogViews::OverlayView::GetHeightForContentsForWidth(int w) {
592 // In this case, 0 means "no preference".
593 if (!message_stack_->visible())
594 return 0;
595
596 return kOverlayImageBottomMargin +
597 views::kButtonVEdgeMarginNew +
598 message_stack_->GetHeightForWidth(w) +
599 image_view_->GetHeightForWidth(w) +
600 (button_->visible() ? button_->GetHeightForWidth(w) +
601 views::kButtonVEdgeMarginNew : 0);
602}
603
Ben Murdoch7dbb3d52013-07-17 14:55:54 +0100604void AutofillDialogViews::OverlayView::SetState(
605 const DialogOverlayState& state,
606 views::ButtonListener* listener) {
607 // Don't update anything if we're still fading out the old state.
608 if (fade_out_)
609 return;
610
611 if (state.image.IsEmpty()) {
612 SetVisible(false);
613 return;
614 }
615
616 image_view_->SetImage(state.image.ToImageSkia());
617
618 message_stack_->RemoveAllChildViews(true);
619 for (size_t i = 0; i < state.strings.size(); ++i) {
620 views::Label* label = new views::Label();
621 label->SetAutoColorReadabilityEnabled(false);
622 label->SetMultiLine(true);
623 label->SetText(state.strings[i].text);
624 label->SetFont(state.strings[i].font);
625 label->SetEnabledColor(state.strings[i].text_color);
626 label->SetHorizontalAlignment(state.strings[i].alignment);
627 message_stack_->AddChildView(label);
628 }
629 message_stack_->SetVisible(message_stack_->child_count() > 0);
630
631 button_->SetVisible(!state.button_text.empty());
632 if (!state.button_text.empty())
633 button_->SetText(state.button_text);
634
635 SetVisible(true);
636 if (parent())
637 parent()->Layout();
638}
639
640void AutofillDialogViews::OverlayView::BeginFadeOut() {
641 Parts parts;
642 // For this part of the animation, simply show the splash image.
643 parts.push_back(Part(kSplashDisplayDurationMs, ui::Tween::ZERO));
644 // For this part of the animation, fade out the splash image.
645 parts.push_back(Part(kSplashFadeOutDurationMs, ui::Tween::EASE_IN));
646 // For this part of the animation, fade out |this| (fade in the dialog).
647 parts.push_back(Part(kSplashFadeInDialogDurationMs, ui::Tween::EASE_OUT));
648 fade_out_.reset(
649 new ui::MultiAnimation(parts,
650 ui::MultiAnimation::GetDefaultTimerInterval()));
651 fade_out_->set_delegate(this);
652 fade_out_->set_continuous(false);
653 fade_out_->Start();
654}
655
656void AutofillDialogViews::OverlayView::AnimationProgressed(
657 const ui::Animation* animation) {
658 DCHECK_EQ(animation, fade_out_.get());
659 if (fade_out_->current_part_index() != 0)
660 SchedulePaint();
661}
662
663void AutofillDialogViews::OverlayView::AnimationEnded(
664 const ui::Animation* animation) {
665 DCHECK_EQ(animation, fade_out_.get());
666 SetVisible(false);
667 fade_out_.reset();
668}
669
Ben Murdoch58e6fbe2013-07-26 10:20:38 +0100670gfx::Insets AutofillDialogViews::OverlayView::GetInsets() const {
671 return gfx::Insets(12, 12, 12, 12);
672}
673
Ben Murdoch7dbb3d52013-07-17 14:55:54 +0100674void AutofillDialogViews::OverlayView::Layout() {
Ben Murdoch58e6fbe2013-07-26 10:20:38 +0100675 gfx::Rect bounds = ContentBoundsSansBubbleBorder();
Ben Murdoch7dbb3d52013-07-17 14:55:54 +0100676 if (!message_stack_->visible()) {
677 image_view_->SetBoundsRect(bounds);
678 return;
679 }
680
681 int y = bounds.bottom() - views::kButtonVEdgeMarginNew;
682 if (button_->visible()) {
683 button_->SizeToPreferredSize();
684 y -= button_->height();
685 button_->SetPosition(gfx::Point(
Ben Murdochca12bfa2013-07-23 11:17:05 +0100686 bounds.CenterPoint().x() - button_->width() / 2, y));
Ben Murdoch7dbb3d52013-07-17 14:55:54 +0100687 y -= views::kButtonVEdgeMarginNew;
688 }
689
690 int message_height = message_stack_->GetHeightForWidth(bounds.width());
691 y -= message_height;
692 message_stack_->SetBounds(bounds.x(), y, bounds.width(), message_height);
693
694 gfx::Size image_size = image_view_->GetPreferredSize();
Ben Murdoch558790d2013-07-30 15:19:42 +0100695 y -= image_size.height() + kOverlayImageBottomMargin;
Ben Murdoch7dbb3d52013-07-17 14:55:54 +0100696 image_view_->SetBounds(bounds.x(), y, bounds.width(), image_size.height());
697}
698
699const char* AutofillDialogViews::OverlayView::GetClassName() const {
700 return kOverlayViewClassName;
701}
702
703void AutofillDialogViews::OverlayView::OnPaint(gfx::Canvas* canvas) {
704 // BubbleFrameView doesn't mask the window, it just draws the border via
705 // image assets. Match that rounding here.
Ben Murdoch58e6fbe2013-07-26 10:20:38 +0100706 gfx::Rect rect = ContentBoundsSansBubbleBorder();
707 const SkScalar kCornerRadius = SkIntToScalar(
708 GetBubbleBorder() ? GetBubbleBorder()->GetBorderCornerRadius() : 2);
Ben Murdoch7dbb3d52013-07-17 14:55:54 +0100709 gfx::Path window_mask;
710 window_mask.addRoundRect(gfx::RectToSkRect(rect),
711 kCornerRadius, kCornerRadius);
712 canvas->ClipPath(window_mask);
713
714 // Fade out background (i.e. fade in what's behind |this|).
715 if (fade_out_ && fade_out_->current_part_index() == 2)
716 canvas->SaveLayerAlpha((1 - fade_out_->GetCurrentValue()) * 255);
717
718 OnPaintBackground(canvas);
719
720 // Draw the arrow, border, and fill for the bottom area.
721 if (message_stack_->visible()) {
722 const int arrow_half_width = kArrowWidth / 2.0f;
723 SkPath arrow;
724 int y = message_stack_->y() - 1;
725 // Note that we purposely draw slightly outside of |rect| so that the
726 // stroke is hidden on the sides.
727 arrow.moveTo(rect.x() - 1, y);
728 arrow.rLineTo(rect.width() / 2 - arrow_half_width, 0);
729 arrow.rLineTo(arrow_half_width, -kArrowHeight);
730 arrow.rLineTo(arrow_half_width, kArrowHeight);
731 arrow.lineTo(rect.right() + 1, y);
732 arrow.lineTo(rect.right() + 1, rect.bottom() + 1);
733 arrow.lineTo(rect.x() - 1, rect.bottom() + 1);
734 arrow.close();
735
736 SkPaint paint;
737 paint.setColor(kShadingColor);
738 paint.setStyle(SkPaint::kFill_Style);
739 canvas->DrawPath(arrow, paint);
740 paint.setColor(kSubtleBorderColor);
741 paint.setStyle(SkPaint::kStroke_Style);
742 canvas->DrawPath(arrow, paint);
743 }
744
745 PaintChildren(canvas);
746}
747
748void AutofillDialogViews::OverlayView::PaintChildren(gfx::Canvas* canvas) {
749 // Don't draw children.
750 if (fade_out_ && fade_out_->current_part_index() == 2)
751 return;
752
753 // Fade out children.
754 if (fade_out_ && fade_out_->current_part_index() == 1)
755 canvas->SaveLayerAlpha((1 - fade_out_->GetCurrentValue()) * 255);
756
757 views::View::PaintChildren(canvas);
758}
759
Ben Murdoch58e6fbe2013-07-26 10:20:38 +0100760views::BubbleBorder* AutofillDialogViews::OverlayView::GetBubbleBorder() {
761 views::View* frame = GetWidget()->non_client_view()->frame_view();
762 std::string bubble_frame_view_name(views::BubbleFrameView::kViewClassName);
763 if (frame->GetClassName() == bubble_frame_view_name)
764 return static_cast<views::BubbleFrameView*>(frame)->bubble_border();
765 NOTREACHED();
766 return NULL;
767}
768
769gfx::Rect AutofillDialogViews::OverlayView::ContentBoundsSansBubbleBorder() {
770 gfx::Rect bounds = GetContentsBounds();
771 int bubble_width = 5;
772 if (GetBubbleBorder())
773 bubble_width = GetBubbleBorder()->GetBorderThickness();
774 bounds.Inset(bubble_width, bubble_width, bubble_width, bubble_width);
775 return bounds;
776}
777
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000778// AutofillDialogViews::NotificationArea ---------------------------------------
779
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100780AutofillDialogViews::NotificationArea::NotificationArea(
Ben Murdoch32409262013-08-07 11:04:47 +0100781 AutofillDialogViewDelegate* delegate)
782 : delegate_(delegate),
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100783 checkbox_(NULL) {
784 // Reserve vertical space for the arrow (regardless of whether one exists).
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +0100785 // The -1 accounts for the border.
786 set_border(views::Border::CreateEmptyBorder(kArrowHeight - 1, 0, 0, 0));
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100787
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000788 views::BoxLayout* box_layout =
789 new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0);
790 SetLayoutManager(box_layout);
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000791}
792
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000793AutofillDialogViews::NotificationArea::~NotificationArea() {}
794
795void AutofillDialogViews::NotificationArea::SetNotifications(
796 const std::vector<DialogNotification>& notifications) {
797 notifications_ = notifications;
798
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000799 RemoveAllChildViews(true);
800 checkbox_ = NULL;
801
802 if (notifications_.empty())
803 return;
804
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000805 for (size_t i = 0; i < notifications_.size(); ++i) {
806 const DialogNotification& notification = notifications_[i];
Ben Murdoch558790d2013-07-30 15:19:42 +0100807 scoped_ptr<NotificationView> view(new NotificationView(notification));
808 if (view->checkbox())
809 checkbox_ = view->checkbox();
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000810
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000811 AddChildView(view.release());
812 }
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000813
Ben Murdoch558790d2013-07-30 15:19:42 +0100814 if (checkbox_)
815 checkbox_->set_listener(this);
816
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100817 PreferredSizeChanged();
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000818}
819
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100820gfx::Size AutofillDialogViews::NotificationArea::GetPreferredSize() {
821 gfx::Size size = views::View::GetPreferredSize();
822 // Ensure that long notifications wrap and don't enlarge the dialog.
823 size.set_width(1);
824 return size;
825}
826
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +0100827const char* AutofillDialogViews::NotificationArea::GetClassName() const {
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000828 return kNotificationAreaClassName;
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000829}
830
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +0100831void AutofillDialogViews::NotificationArea::PaintChildren(
832 gfx::Canvas* canvas) {}
833
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000834void AutofillDialogViews::NotificationArea::OnPaint(gfx::Canvas* canvas) {
835 views::View::OnPaint(canvas);
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +0100836 views::View::PaintChildren(canvas);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000837
838 if (HasArrow()) {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100839 DrawArrow(
840 canvas,
841 GetMirroredXInView(width() - arrow_centering_anchor_->width() / 2.0f),
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +0100842 notifications_[0].GetBackgroundColor(),
843 notifications_[0].GetBorderColor());
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000844 }
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000845}
846
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100847void AutofillDialogViews::OnWidgetClosing(views::Widget* widget) {
848 observer_.Remove(widget);
849}
850
851void AutofillDialogViews::OnWidgetBoundsChanged(views::Widget* widget,
852 const gfx::Rect& new_bounds) {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100853 ContentsPreferredSizeChanged();
854}
855
856void AutofillDialogViews::NotificationArea::ButtonPressed(
857 views::Button* sender, const ui::Event& event) {
858 DCHECK_EQ(sender, checkbox_);
Ben Murdoch32409262013-08-07 11:04:47 +0100859 delegate_->NotificationCheckboxStateChanged(notifications_.front().type(),
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100860 checkbox_->checked());
861}
862
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000863bool AutofillDialogViews::NotificationArea::HasArrow() {
864 return !notifications_.empty() && notifications_[0].HasArrow() &&
865 arrow_centering_anchor_.get();
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000866}
867
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000868// AutofillDialogViews::SectionContainer ---------------------------------------
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000869
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000870AutofillDialogViews::SectionContainer::SectionContainer(
Ben Murdoch558790d2013-07-30 15:19:42 +0100871 const base::string16& label,
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000872 views::View* controls,
873 views::Button* proxy_button)
874 : proxy_button_(proxy_button),
875 forward_mouse_events_(false) {
876 set_notify_enter_exit_on_child(true);
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000877
Ben Murdochbbcdd452013-07-25 10:06:34 +0100878 set_border(views::Border::CreateEmptyBorder(kDetailSectionVerticalPadding,
Ben Murdoch558790d2013-07-30 15:19:42 +0100879 kDialogEdgePadding,
Ben Murdochbbcdd452013-07-25 10:06:34 +0100880 kDetailSectionVerticalPadding,
Ben Murdoch558790d2013-07-30 15:19:42 +0100881 kDialogEdgePadding));
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000882
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +0100883 // TODO(estade): this label should be semi-bold.
Ben Murdochbbcdd452013-07-25 10:06:34 +0100884 views::Label* label_view = new views::Label(label);
885 label_view->SetHorizontalAlignment(gfx::ALIGN_LEFT);
886
887 views::View* label_bar = new views::View();
888 views::GridLayout* label_bar_layout = new views::GridLayout(label_bar);
889 label_bar->SetLayoutManager(label_bar_layout);
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000890 const int kColumnSetId = 0;
Ben Murdochbbcdd452013-07-25 10:06:34 +0100891 views::ColumnSet* columns = label_bar_layout->AddColumnSet(kColumnSetId);
892 // TODO(estade): do something about this '480'.
893 columns->AddColumn(views::GridLayout::LEADING,
894 views::GridLayout::LEADING,
895 0,
896 views::GridLayout::FIXED,
897 480,
898 0);
899 columns->AddColumn(views::GridLayout::LEADING,
900 views::GridLayout::LEADING,
901 0,
902 views::GridLayout::USE_PREF,
903 0,
904 0);
905 label_bar_layout->StartRow(0, kColumnSetId);
906 label_bar_layout->AddView(label_view);
907 label_bar_layout->AddView(proxy_button);
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000908
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +0100909 SetLayoutManager(new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0));
Ben Murdochbbcdd452013-07-25 10:06:34 +0100910 AddChildView(label_bar);
911 AddChildView(controls);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000912}
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000913
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000914AutofillDialogViews::SectionContainer::~SectionContainer() {}
915
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100916void AutofillDialogViews::SectionContainer::SetActive(bool active) {
917 bool is_active = active && proxy_button_->visible();
918 if (is_active == !!background())
919 return;
920
921 set_background(is_active ?
922 views::Background::CreateSolidBackground(kShadingColor) :
923 NULL);
924 SchedulePaint();
925}
926
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000927void AutofillDialogViews::SectionContainer::SetForwardMouseEvents(
928 bool forward) {
929 forward_mouse_events_ = forward;
930 if (!forward)
931 set_background(NULL);
932}
933
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000934void AutofillDialogViews::SectionContainer::OnMouseMoved(
935 const ui::MouseEvent& event) {
936 if (!forward_mouse_events_)
937 return;
938
939 SetActive(true);
940}
941
942void AutofillDialogViews::SectionContainer::OnMouseEntered(
943 const ui::MouseEvent& event) {
944 if (!forward_mouse_events_)
945 return;
946
947 SetActive(true);
948 proxy_button_->OnMouseEntered(ProxyEvent(event));
949 SchedulePaint();
950}
951
952void AutofillDialogViews::SectionContainer::OnMouseExited(
953 const ui::MouseEvent& event) {
954 if (!forward_mouse_events_)
955 return;
956
957 SetActive(false);
958 proxy_button_->OnMouseExited(ProxyEvent(event));
959 SchedulePaint();
960}
961
962bool AutofillDialogViews::SectionContainer::OnMousePressed(
963 const ui::MouseEvent& event) {
964 if (!forward_mouse_events_)
965 return false;
966
967 return proxy_button_->OnMousePressed(ProxyEvent(event));
968}
969
970void AutofillDialogViews::SectionContainer::OnMouseReleased(
971 const ui::MouseEvent& event) {
972 if (!forward_mouse_events_)
973 return;
974
975 proxy_button_->OnMouseReleased(ProxyEvent(event));
976}
977
978// static
979ui::MouseEvent AutofillDialogViews::SectionContainer::ProxyEvent(
980 const ui::MouseEvent& event) {
981 ui::MouseEvent event_copy = event;
982 event_copy.set_location(gfx::Point());
983 return event_copy;
984}
985
986// AutofilDialogViews::SuggestionView ------------------------------------------
987
988AutofillDialogViews::SuggestionView::SuggestionView(
Ben Murdoch558790d2013-07-30 15:19:42 +0100989 const base::string16& edit_label,
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000990 AutofillDialogViews* autofill_dialog)
991 : label_(new views::Label()),
992 label_line_2_(new views::Label()),
993 icon_(new views::ImageView()),
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100994 label_container_(new SectionRowView()),
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000995 decorated_(
Ben Murdoch558790d2013-07-30 15:19:42 +0100996 new DecoratedTextfield(base::string16(),
997 base::string16(),
998 autofill_dialog)) {
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +0100999 // TODO(estade): Make this the correct color.
1000 set_border(
1001 views::Border::CreateSolidSidedBorder(1, 0, 0, 0, SK_ColorLTGRAY));
1002
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001003 // Label and icon.
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001004 label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001005 label_container_->AddChildView(icon_);
1006 label_container_->AddChildView(label_);
1007 label_container_->AddChildView(decorated_);
1008 decorated_->SetVisible(false);
1009 // TODO(estade): get the sizing and spacing right on this textfield.
Ben Murdocheb525c52013-07-10 11:40:50 +01001010 decorated_->set_default_width_in_chars(10);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001011 AddChildView(label_container_);
1012
1013 label_line_2_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
1014 label_line_2_->SetVisible(false);
Ben Murdocheb525c52013-07-10 11:40:50 +01001015 label_line_2_->SetMultiLine(true);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001016 AddChildView(label_line_2_);
1017
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +01001018 // TODO(estade): do something about this '2'.
1019 SetLayoutManager(new views::BoxLayout(views::BoxLayout::kVertical, 0, 2, 0));
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001020}
1021
1022AutofillDialogViews::SuggestionView::~SuggestionView() {}
1023
1024void AutofillDialogViews::SuggestionView::SetSuggestionText(
Ben Murdoch558790d2013-07-30 15:19:42 +01001025 const base::string16& text,
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001026 gfx::Font::FontStyle text_style) {
1027 label_->SetFont(ui::ResourceBundle::GetSharedInstance().GetFont(
1028 ui::ResourceBundle::BaseFont).DeriveFont(0, text_style));
1029
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001030 // TODO(estade): does this localize well?
Ben Murdoch558790d2013-07-30 15:19:42 +01001031 base::string16 line_return(ASCIIToUTF16("\n"));
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001032 size_t position = text.find(line_return);
Ben Murdoch558790d2013-07-30 15:19:42 +01001033 if (position == base::string16::npos) {
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001034 label_->SetText(text);
1035 label_line_2_->SetVisible(false);
1036 } else {
1037 label_->SetText(text.substr(0, position));
1038 label_line_2_->SetText(text.substr(position + line_return.length()));
1039 label_line_2_->SetVisible(true);
1040 }
1041}
1042
1043void AutofillDialogViews::SuggestionView::SetSuggestionIcon(
1044 const gfx::Image& image) {
1045 icon_->SetVisible(!image.IsEmpty());
1046 icon_->SetImage(image.AsImageSkia());
1047}
1048
1049void AutofillDialogViews::SuggestionView::ShowTextfield(
Ben Murdoch558790d2013-07-30 15:19:42 +01001050 const base::string16& placeholder_text,
Ben Murdocheb525c52013-07-10 11:40:50 +01001051 const gfx::Image& icon) {
1052 decorated_->set_placeholder_text(placeholder_text);
1053 decorated_->SetIcon(icon);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001054 decorated_->SetVisible(true);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001055 // The textfield will increase the height of the first row and cause the
1056 // label to be aligned properly, so the border is not necessary.
1057 label_->set_border(NULL);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001058}
1059
Ben Murdocheb525c52013-07-10 11:40:50 +01001060// AutofillDialogViews::AutocheckoutStepsArea ---------------------------------
1061
1062AutofillDialogViews::AutocheckoutStepsArea::AutocheckoutStepsArea() {
1063 SetLayoutManager(new views::BoxLayout(views::BoxLayout::kVertical,
1064 kAutocheckoutStepsAreaPadding,
1065 0,
1066 kAutocheckoutStepInset));
1067}
1068
1069void AutofillDialogViews::AutocheckoutStepsArea::SetSteps(
1070 const std::vector<DialogAutocheckoutStep>& steps) {
1071 RemoveAllChildViews(true);
1072 for (size_t i = 0; i < steps.size(); ++i) {
1073 const DialogAutocheckoutStep& step = steps[i];
1074 AutocheckoutStepProgressView* progressView =
1075 new AutocheckoutStepProgressView(step.GetDisplayText(),
1076 step.GetTextFont(),
1077 step.GetTextColor(),
1078 step.IsIconVisible());
1079
1080 AddChildView(progressView);
1081 }
1082
1083 PreferredSizeChanged();
1084}
1085
1086// AutofillDialogViews::AutocheckoutProgressBar
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001087
1088AutofillDialogViews::AutocheckoutProgressBar::AutocheckoutProgressBar() {}
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +01001089AutofillDialogViews::AutocheckoutProgressBar::~AutocheckoutProgressBar() {}
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001090
1091gfx::Size AutofillDialogViews::AutocheckoutProgressBar::GetPreferredSize() {
1092 return gfx::Size(kAutocheckoutProgressBarWidth,
1093 kAutocheckoutProgressBarHeight);
1094}
1095
1096// AutofillDialogView ----------------------------------------------------------
1097
1098// static
1099AutofillDialogView* AutofillDialogView::Create(
Ben Murdoch32409262013-08-07 11:04:47 +01001100 AutofillDialogViewDelegate* delegate) {
1101 return new AutofillDialogViews(delegate);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001102}
1103
1104// AutofillDialogViews ---------------------------------------------------------
1105
Ben Murdoch32409262013-08-07 11:04:47 +01001106AutofillDialogViews::AutofillDialogViews(AutofillDialogViewDelegate* delegate)
1107 : delegate_(delegate),
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001108 window_(NULL),
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001109 notification_area_(NULL),
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001110 account_chooser_(NULL),
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001111 sign_in_webview_(NULL),
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001112 scrollable_area_(NULL),
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001113 details_container_(NULL),
Ben Murdocheb525c52013-07-10 11:40:50 +01001114 loading_shield_(NULL),
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +01001115 overlay_view_(NULL),
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001116 button_strip_extra_view_(NULL),
1117 save_in_chrome_checkbox_(NULL),
Ben Murdochbb1529c2013-08-08 10:24:53 +01001118 save_in_chrome_checkbox_container_(NULL),
Ben Murdoch2385ea32013-08-06 11:01:04 +01001119 button_strip_image_(NULL),
Ben Murdocheb525c52013-07-10 11:40:50 +01001120 autocheckout_steps_area_(NULL),
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001121 autocheckout_progress_bar_view_(NULL),
1122 autocheckout_progress_bar_(NULL),
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001123 footnote_view_(NULL),
1124 legal_document_view_(NULL),
1125 focus_manager_(NULL),
1126 observer_(this) {
Ben Murdoch32409262013-08-07 11:04:47 +01001127 DCHECK(delegate);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001128 detail_groups_.insert(std::make_pair(SECTION_EMAIL,
1129 DetailsGroup(SECTION_EMAIL)));
1130 detail_groups_.insert(std::make_pair(SECTION_CC,
1131 DetailsGroup(SECTION_CC)));
1132 detail_groups_.insert(std::make_pair(SECTION_BILLING,
1133 DetailsGroup(SECTION_BILLING)));
1134 detail_groups_.insert(std::make_pair(SECTION_CC_BILLING,
1135 DetailsGroup(SECTION_CC_BILLING)));
1136 detail_groups_.insert(std::make_pair(SECTION_SHIPPING,
1137 DetailsGroup(SECTION_SHIPPING)));
1138}
1139
1140AutofillDialogViews::~AutofillDialogViews() {
1141 DCHECK(!window_);
1142}
1143
1144void AutofillDialogViews::Show() {
1145 InitChildViews();
1146 UpdateAccountChooser();
1147 UpdateNotificationArea();
Ben Murdoch2385ea32013-08-06 11:01:04 +01001148 UpdateButtonStripExtraView();
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001149
1150 // Ownership of |contents_| is handed off by this call. The widget will take
1151 // care of deleting itself after calling DeleteDelegate().
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001152 WebContentsModalDialogManager* web_contents_modal_dialog_manager =
1153 WebContentsModalDialogManager::FromWebContents(
Ben Murdochbb1529c2013-08-08 10:24:53 +01001154 delegate_->GetWebContents());
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001155 window_ = CreateWebContentsModalDialogViews(
1156 this,
Ben Murdochbb1529c2013-08-08 10:24:53 +01001157 delegate_->GetWebContents()->GetView()->GetNativeView(),
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001158 web_contents_modal_dialog_manager->delegate()->
1159 GetWebContentsModalDialogHost());
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001160 web_contents_modal_dialog_manager->ShowDialog(window_->GetNativeView());
1161 focus_manager_ = window_->GetFocusManager();
1162 focus_manager_->AddFocusChangeListener(this);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001163
1164 // Listen for size changes on the browser.
1165 views::Widget* browser_widget =
1166 views::Widget::GetTopLevelWidgetForNativeView(
Ben Murdochbb1529c2013-08-08 10:24:53 +01001167 delegate_->GetWebContents()->GetView()->GetNativeView());
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001168 observer_.Add(browser_widget);
Ben Murdoch7dbb3d52013-07-17 14:55:54 +01001169
Ben Murdoch32409262013-08-07 11:04:47 +01001170 gfx::Image splash_image = delegate_->SplashPageImage();
Ben Murdoch7dbb3d52013-07-17 14:55:54 +01001171 if (!splash_image.IsEmpty()) {
1172 DialogOverlayState state;
1173 state.image = splash_image;
1174 overlay_view_->SetState(state, NULL);
1175 overlay_view_->BeginFadeOut();
1176 }
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001177}
1178
1179void AutofillDialogViews::Hide() {
1180 if (window_)
1181 window_->Close();
1182}
1183
1184void AutofillDialogViews::UpdateAccountChooser() {
1185 account_chooser_->Update();
Ben Murdocheb525c52013-07-10 11:40:50 +01001186 // TODO(estade): replace this with a better loading image/animation.
1187 // See http://crbug.com/230932
Ben Murdoch32409262013-08-07 11:04:47 +01001188 base::string16 new_loading_message = (delegate_->ShouldShowSpinner() ?
Ben Murdocheb525c52013-07-10 11:40:50 +01001189 ASCIIToUTF16("Loading...") : base::string16());
1190 if (new_loading_message != loading_shield_->text()) {
1191 loading_shield_->SetText(new_loading_message);
1192 loading_shield_->SetVisible(!new_loading_message.empty());
1193 Layout();
1194 }
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001195
1196 // Update legal documents for the account.
1197 if (footnote_view_) {
Ben Murdoch32409262013-08-07 11:04:47 +01001198 const base::string16 text = delegate_->LegalDocumentsText();
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +01001199 legal_document_view_->SetText(text);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001200
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +01001201 if (!text.empty()) {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001202 const std::vector<ui::Range>& link_ranges =
Ben Murdoch32409262013-08-07 11:04:47 +01001203 delegate_->LegalDocumentLinks();
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001204 for (size_t i = 0; i < link_ranges.size(); ++i) {
1205 legal_document_view_->AddStyleRange(
1206 link_ranges[i],
1207 views::StyledLabel::RangeStyleInfo::CreateForLink());
1208 }
1209 }
1210
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +01001211 footnote_view_->SetVisible(!text.empty());
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001212 ContentsPreferredSizeChanged();
1213 }
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001214}
1215
Ben Murdocheb525c52013-07-10 11:40:50 +01001216void AutofillDialogViews::UpdateAutocheckoutStepsArea() {
Ben Murdoch32409262013-08-07 11:04:47 +01001217 autocheckout_steps_area_->SetSteps(delegate_->CurrentAutocheckoutSteps());
Ben Murdocheb525c52013-07-10 11:40:50 +01001218 ContentsPreferredSizeChanged();
1219}
1220
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001221void AutofillDialogViews::UpdateButtonStrip() {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001222 button_strip_extra_view_->SetVisible(
1223 GetDialogButtons() != ui::DIALOG_BUTTON_NONE);
Ben Murdoch2385ea32013-08-06 11:01:04 +01001224 UpdateButtonStripExtraView();
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001225 GetDialogClientView()->UpdateDialogButtons();
Ben Murdoch7dbb3d52013-07-17 14:55:54 +01001226
Ben Murdoch32409262013-08-07 11:04:47 +01001227 overlay_view_->SetState(delegate_->GetDialogOverlay(), this);
Ben Murdoch7dbb3d52013-07-17 14:55:54 +01001228
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001229 ContentsPreferredSizeChanged();
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001230}
1231
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +01001232void AutofillDialogViews::UpdateDetailArea() {
Ben Murdoch32409262013-08-07 11:04:47 +01001233 scrollable_area_->SetVisible(delegate_->ShouldShowDetailArea());
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +01001234 ContentsPreferredSizeChanged();
1235}
1236
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +01001237void AutofillDialogViews::UpdateForErrors() {
1238 ValidateForm();
1239}
1240
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001241void AutofillDialogViews::UpdateNotificationArea() {
1242 DCHECK(notification_area_);
Ben Murdoch32409262013-08-07 11:04:47 +01001243 notification_area_->SetNotifications(delegate_->CurrentNotifications());
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001244 ContentsPreferredSizeChanged();
1245}
1246
1247void AutofillDialogViews::UpdateSection(DialogSection section) {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001248 UpdateSectionImpl(section, true);
1249}
1250
1251void AutofillDialogViews::FillSection(DialogSection section,
1252 const DetailInput& originating_input) {
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001253 DetailsGroup* group = GroupForSection(section);
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +01001254 // Make sure to overwrite the originating input.
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001255 TextfieldMap::iterator text_mapping =
1256 group->textfields.find(&originating_input);
1257 if (text_mapping != group->textfields.end())
Ben Murdoch558790d2013-07-30 15:19:42 +01001258 text_mapping->second->SetText(base::string16());
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001259
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +01001260 // If the Autofill data comes from a credit card, make sure to overwrite the
1261 // CC comboboxes (even if they already have something in them). If the
1262 // Autofill data comes from an AutofillProfile, leave the comboboxes alone.
1263 if ((section == SECTION_CC || section == SECTION_CC_BILLING) &&
Ben Murdoch2385ea32013-08-06 11:01:04 +01001264 AutofillType(originating_input.type).group() == CREDIT_CARD) {
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +01001265 for (ComboboxMap::const_iterator it = group->comboboxes.begin();
1266 it != group->comboboxes.end(); ++it) {
Ben Murdoch2385ea32013-08-06 11:01:04 +01001267 if (AutofillType(it->first->type).group() == CREDIT_CARD)
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +01001268 it->second->SetSelectedIndex(it->second->model()->GetDefaultIndex());
1269 }
1270 }
1271
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001272 UpdateSectionImpl(section, false);
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001273}
1274
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001275void AutofillDialogViews::GetUserInput(DialogSection section,
1276 DetailOutputMap* output) {
1277 DetailsGroup* group = GroupForSection(section);
1278 for (TextfieldMap::const_iterator it = group->textfields.begin();
1279 it != group->textfields.end(); ++it) {
Ben Murdocheb525c52013-07-10 11:40:50 +01001280 output->insert(std::make_pair(it->first, it->second->text()));
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001281 }
1282 for (ComboboxMap::const_iterator it = group->comboboxes.begin();
1283 it != group->comboboxes.end(); ++it) {
1284 output->insert(std::make_pair(it->first,
1285 it->second->model()->GetItemAt(it->second->selected_index())));
1286 }
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001287}
1288
Ben Murdoch558790d2013-07-30 15:19:42 +01001289base::string16 AutofillDialogViews::GetCvc() {
Ben Murdoch32409262013-08-07 11:04:47 +01001290 DialogSection billing_section = delegate_->SectionIsActive(SECTION_CC) ?
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001291 SECTION_CC : SECTION_CC_BILLING;
1292 return GroupForSection(billing_section)->suggested_info->
Ben Murdocheb525c52013-07-10 11:40:50 +01001293 decorated_textfield()->text();
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001294}
1295
1296bool AutofillDialogViews::SaveDetailsLocally() {
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +01001297 DCHECK(save_in_chrome_checkbox_->visible());
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001298 return save_in_chrome_checkbox_->checked();
1299}
1300
1301const content::NavigationController* AutofillDialogViews::ShowSignIn() {
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001302 // TODO(abodenha) We should be able to use the WebContents of the WebView
1303 // to navigate instead of LoadInitialURL. Figure out why it doesn't work.
1304
1305 sign_in_webview_->LoadInitialURL(wallet::GetSignInUrl());
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +01001306
Torne (Richard Coles)a93a17c2013-05-15 11:34:50 +01001307 sign_in_webview_->SetVisible(true);
Ben Murdoch7dbb3d52013-07-17 14:55:54 +01001308 sign_in_webview_->RequestFocus();
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001309 UpdateButtonStrip();
1310 ContentsPreferredSizeChanged();
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001311 return &sign_in_webview_->web_contents()->GetController();
1312}
1313
1314void AutofillDialogViews::HideSignIn() {
Torne (Richard Coles)a93a17c2013-05-15 11:34:50 +01001315 sign_in_webview_->SetVisible(false);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001316 UpdateButtonStrip();
1317 ContentsPreferredSizeChanged();
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001318}
1319
1320void AutofillDialogViews::UpdateProgressBar(double value) {
1321 autocheckout_progress_bar_->SetValue(value);
1322}
1323
1324void AutofillDialogViews::ModelChanged() {
1325 menu_runner_.reset();
1326
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001327 for (DetailGroupMap::const_iterator iter = detail_groups_.begin();
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001328 iter != detail_groups_.end(); ++iter) {
1329 UpdateDetailsGroupState(iter->second);
1330 }
1331}
1332
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001333TestableAutofillDialogView* AutofillDialogViews::GetTestableView() {
1334 return this;
1335}
1336
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +01001337void AutofillDialogViews::OnSignInResize(const gfx::Size& pref_size) {
1338 sign_in_webview_->SetPreferredSize(pref_size);
1339 ContentsPreferredSizeChanged();
1340}
1341
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001342void AutofillDialogViews::SubmitForTesting() {
1343 Accept();
1344}
1345
1346void AutofillDialogViews::CancelForTesting() {
Ben Murdoch7dbb3d52013-07-17 14:55:54 +01001347 GetDialogClientView()->CancelWindow();
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001348}
1349
Ben Murdoch558790d2013-07-30 15:19:42 +01001350base::string16 AutofillDialogViews::GetTextContentsOfInput(
1351 const DetailInput& input) {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001352 views::Textfield* textfield = TextfieldForInput(input);
1353 if (textfield)
1354 return textfield->text();
1355
1356 views::Combobox* combobox = ComboboxForInput(input);
1357 if (combobox)
1358 return combobox->model()->GetItemAt(combobox->selected_index());
1359
1360 NOTREACHED();
Ben Murdoch558790d2013-07-30 15:19:42 +01001361 return base::string16();
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001362}
1363
Ben Murdoch558790d2013-07-30 15:19:42 +01001364void AutofillDialogViews::SetTextContentsOfInput(
1365 const DetailInput& input,
1366 const base::string16& contents) {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001367 views::Textfield* textfield = TextfieldForInput(input);
1368 if (textfield) {
1369 TextfieldForInput(input)->SetText(contents);
1370 return;
1371 }
1372
1373 views::Combobox* combobox = ComboboxForInput(input);
1374 if (combobox) {
1375 for (int i = 0; i < combobox->model()->GetItemCount(); ++i) {
1376 if (contents == combobox->model()->GetItemAt(i)) {
1377 combobox->SetSelectedIndex(i);
1378 return;
1379 }
1380 }
1381 // If we don't find a match, return the combobox to its default state.
1382 combobox->SetSelectedIndex(combobox->model()->GetDefaultIndex());
1383 return;
1384 }
1385
1386 NOTREACHED();
1387}
1388
Ben Murdocheb525c52013-07-10 11:40:50 +01001389void AutofillDialogViews::SetTextContentsOfSuggestionInput(
1390 DialogSection section,
1391 const base::string16& text) {
1392 GroupForSection(section)->suggested_info->decorated_textfield()->
1393 SetText(text);
1394}
1395
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001396void AutofillDialogViews::ActivateInput(const DetailInput& input) {
1397 TextfieldEditedOrActivated(TextfieldForInput(input), false);
1398}
1399
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +01001400gfx::Size AutofillDialogViews::GetSize() const {
1401 return GetWidget() ? GetWidget()->GetRootView()->size() : gfx::Size();
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +01001402}
1403
Ben Murdocheb525c52013-07-10 11:40:50 +01001404gfx::Size AutofillDialogViews::GetPreferredSize() {
1405 gfx::Insets insets = GetInsets();
1406 gfx::Size scroll_size = scrollable_area_->contents()->GetPreferredSize();
1407 int width = scroll_size.width() + insets.width();
1408
1409 if (sign_in_webview_->visible()) {
1410 gfx::Size size = static_cast<views::View*>(sign_in_webview_)->
1411 GetPreferredSize();
1412 return gfx::Size(width, size.height() + insets.height());
1413 }
1414
1415 int base_height = insets.height();
1416 int notification_height = notification_area_->
1417 GetHeightForWidth(scroll_size.width());
1418 if (notification_height > 0)
1419 base_height += notification_height + views::kRelatedControlVerticalSpacing;
1420
1421 int steps_height = autocheckout_steps_area_->
1422 GetHeightForWidth(scroll_size.width());
1423 if (steps_height > 0)
1424 base_height += steps_height + views::kRelatedControlVerticalSpacing;
1425
Ben Murdoch558790d2013-07-30 15:19:42 +01001426 gfx::Size preferred_size;
Ben Murdocheb525c52013-07-10 11:40:50 +01001427 // When the scroll area isn't visible, it still sets the width but doesn't
1428 // factor into height.
Ben Murdoch558790d2013-07-30 15:19:42 +01001429 if (!scrollable_area_->visible()) {
1430 preferred_size.SetSize(width, base_height);
1431 } else {
1432 // Show as much of the scroll view as is possible without going past the
1433 // bottom of the browser window.
1434 views::Widget* widget =
1435 views::Widget::GetTopLevelWidgetForNativeView(
Ben Murdochbb1529c2013-08-08 10:24:53 +01001436 delegate_->GetWebContents()->GetView()->GetNativeView());
Ben Murdoch558790d2013-07-30 15:19:42 +01001437 int browser_window_height =
1438 widget ? widget->GetContentsView()->bounds().height() : INT_MAX;
1439 const int kWindowDecorationHeight = 200;
1440 int height = base_height + std::min(
1441 scroll_size.height(),
1442 std::max(kMinimumContentsHeight,
1443 browser_window_height - base_height -
1444 kWindowDecorationHeight));
1445 preferred_size.SetSize(width, height);
1446 }
Ben Murdocheb525c52013-07-10 11:40:50 +01001447
Ben Murdoch558790d2013-07-30 15:19:42 +01001448 if (!overlay_view_->visible())
1449 return preferred_size;
Ben Murdocheb525c52013-07-10 11:40:50 +01001450
Ben Murdoch558790d2013-07-30 15:19:42 +01001451 int height_of_overlay =
1452 overlay_view_->GetHeightForContentsForWidth(preferred_size.width());
1453 if (height_of_overlay > 0)
1454 preferred_size.set_height(height_of_overlay);
1455
1456 return preferred_size;
Ben Murdocheb525c52013-07-10 11:40:50 +01001457}
1458
1459void AutofillDialogViews::Layout() {
1460 gfx::Rect content_bounds = GetContentsBounds();
1461 if (sign_in_webview_->visible()) {
1462 sign_in_webview_->SetBoundsRect(content_bounds);
1463 return;
1464 }
1465
1466 const int x = content_bounds.x();
1467 const int y = content_bounds.y();
1468 const int w = content_bounds.width();
1469 // Layout notification area at top of dialog.
1470 int notification_height = notification_area_->GetHeightForWidth(w);
1471 notification_area_->SetBounds(x, y, w, notification_height);
1472
1473 // Layout Autocheckout steps at bottom of dialog.
1474 int steps_height = autocheckout_steps_area_->GetHeightForWidth(w);
1475 autocheckout_steps_area_->SetBounds(x, content_bounds.bottom() - steps_height,
1476 w, steps_height);
1477
1478 // The rest (the |scrollable_area_|) takes up whatever's left.
1479 if (scrollable_area_->visible()) {
1480 int scroll_y = y;
1481 if (notification_height > 0)
1482 scroll_y += notification_height + views::kRelatedControlVerticalSpacing;
1483
1484 int scroll_bottom = content_bounds.bottom();
1485 if (steps_height > 0)
1486 scroll_bottom -= steps_height + views::kRelatedControlVerticalSpacing;
1487
1488 scrollable_area_->contents()->SizeToPreferredSize();
1489 scrollable_area_->SetBounds(x, scroll_y, w, scroll_bottom - scroll_y);
1490 }
1491
1492 if (loading_shield_->visible())
1493 loading_shield_->SetBoundsRect(bounds());
1494
1495 if (error_bubble_)
1496 error_bubble_->UpdatePosition();
1497}
1498
1499void AutofillDialogViews::OnBoundsChanged(const gfx::Rect& previous_bounds) {
1500 sign_in_delegate_->SetMinWidth(GetContentsBounds().width());
1501}
1502
Ben Murdoch558790d2013-07-30 15:19:42 +01001503base::string16 AutofillDialogViews::GetWindowTitle() const {
Ben Murdoch32409262013-08-07 11:04:47 +01001504 return delegate_->DialogTitle();
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001505}
1506
1507void AutofillDialogViews::WindowClosing() {
1508 focus_manager_->RemoveFocusChangeListener(this);
1509}
1510
1511void AutofillDialogViews::DeleteDelegate() {
1512 window_ = NULL;
Ben Murdoch32409262013-08-07 11:04:47 +01001513 // |this| belongs to the controller (|delegate_|).
1514 delegate_->ViewClosed();
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001515}
1516
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001517int AutofillDialogViews::GetDialogButtons() const {
Torne (Richard Coles)a93a17c2013-05-15 11:34:50 +01001518 if (sign_in_webview_->visible())
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001519 return ui::DIALOG_BUTTON_NONE;
1520
Ben Murdoch32409262013-08-07 11:04:47 +01001521 return delegate_->GetDialogButtons();
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001522}
1523
Ben Murdoch558790d2013-07-30 15:19:42 +01001524base::string16 AutofillDialogViews::GetDialogButtonLabel(
1525 ui::DialogButton button) const {
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001526 return button == ui::DIALOG_BUTTON_OK ?
Ben Murdoch32409262013-08-07 11:04:47 +01001527 delegate_->ConfirmButtonText() : delegate_->CancelButtonText();
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001528}
1529
Ben Murdochca12bfa2013-07-23 11:17:05 +01001530bool AutofillDialogViews::ShouldDefaultButtonBeBlue() const {
1531 return true;
1532}
1533
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001534bool AutofillDialogViews::IsDialogButtonEnabled(ui::DialogButton button) const {
Ben Murdoch32409262013-08-07 11:04:47 +01001535 return delegate_->IsDialogButtonEnabled(button);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001536}
1537
1538views::View* AutofillDialogViews::CreateExtraView() {
1539 return button_strip_extra_view_;
1540}
1541
1542views::View* AutofillDialogViews::CreateTitlebarExtraView() {
1543 return account_chooser_;
1544}
1545
1546views::View* AutofillDialogViews::CreateFootnoteView() {
Ben Murdocheb525c52013-07-10 11:40:50 +01001547 footnote_view_ = new LayoutPropagationView();
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001548 footnote_view_->SetLayoutManager(
1549 new views::BoxLayout(views::BoxLayout::kVertical,
Ben Murdoch558790d2013-07-30 15:19:42 +01001550 kDialogEdgePadding,
1551 kDialogEdgePadding,
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001552 0));
1553 footnote_view_->set_border(
1554 views::Border::CreateSolidSidedBorder(1, 0, 0, 0, kSubtleBorderColor));
1555 footnote_view_->set_background(
1556 views::Background::CreateSolidBackground(kShadingColor));
1557
Ben Murdoch558790d2013-07-30 15:19:42 +01001558 legal_document_view_ = new views::StyledLabel(base::string16(), this);
Ben Murdochca12bfa2013-07-23 11:17:05 +01001559 views::StyledLabel::RangeStyleInfo default_style;
1560 default_style.color = kGreyTextColor;
1561 legal_document_view_->SetDefaultStyle(default_style);
Ben Murdoch7dbb3d52013-07-17 14:55:54 +01001562
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001563 footnote_view_->AddChildView(legal_document_view_);
1564 footnote_view_->SetVisible(false);
1565
1566 return footnote_view_;
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001567}
1568
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +01001569views::View* AutofillDialogViews::CreateOverlayView() {
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +01001570 return overlay_view_;
1571}
1572
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001573bool AutofillDialogViews::Cancel() {
Ben Murdoch32409262013-08-07 11:04:47 +01001574 return delegate_->OnCancel();
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001575}
1576
1577bool AutofillDialogViews::Accept() {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001578 if (ValidateForm())
Ben Murdoch32409262013-08-07 11:04:47 +01001579 return delegate_->OnAccept();
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001580
Ben Murdochca12bfa2013-07-23 11:17:05 +01001581 if (!validity_map_.empty())
1582 validity_map_.begin()->first->RequestFocus();
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001583 return false;
1584}
1585
1586// TODO(wittman): Remove this override once we move to the new style frame view
1587// on all dialogs.
1588views::NonClientFrameView* AutofillDialogViews::CreateNonClientFrameView(
1589 views::Widget* widget) {
1590 return CreateConstrainedStyleNonClientFrameView(
1591 widget,
Ben Murdochbb1529c2013-08-08 10:24:53 +01001592 delegate_->GetWebContents()->GetBrowserContext());
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001593}
1594
1595void AutofillDialogViews::ButtonPressed(views::Button* sender,
1596 const ui::Event& event) {
Ben Murdoch7dbb3d52013-07-17 14:55:54 +01001597 if (sender->GetAncestorWithClassName(kOverlayViewClassName)) {
Ben Murdoch32409262013-08-07 11:04:47 +01001598 delegate_->OverlayButtonPressed();
Ben Murdoch7dbb3d52013-07-17 14:55:54 +01001599 return;
1600 }
1601
Torne (Richard Coles)a93a17c2013-05-15 11:34:50 +01001602 // TODO(estade): Should the menu be shown on mouse down?
1603 DetailsGroup* group = NULL;
1604 for (DetailGroupMap::iterator iter = detail_groups_.begin();
1605 iter != detail_groups_.end(); ++iter) {
1606 if (sender == iter->second.suggested_button) {
1607 group = &iter->second;
1608 break;
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001609 }
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001610 }
Torne (Richard Coles)a93a17c2013-05-15 11:34:50 +01001611 DCHECK(group);
1612
1613 if (!group->suggested_button->visible())
1614 return;
1615
1616 menu_runner_.reset(new views::MenuRunner(
Ben Murdoch32409262013-08-07 11:04:47 +01001617 delegate_->MenuModelForSection(group->section)));
Torne (Richard Coles)a93a17c2013-05-15 11:34:50 +01001618
1619 group->container->SetActive(true);
1620 views::Button::ButtonState state = group->suggested_button->state();
1621 group->suggested_button->SetState(views::Button::STATE_PRESSED);
1622 // Ignore the result since we don't need to handle a deleted menu specially.
1623 gfx::Rect bounds = group->suggested_button->GetBoundsInScreen();
1624 bounds.Inset(group->suggested_button->GetInsets());
1625 ignore_result(
1626 menu_runner_->RunMenuAt(sender->GetWidget(),
1627 NULL,
1628 bounds,
1629 views::MenuItemView::TOPRIGHT,
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +01001630 ui::GetMenuSourceTypeForEvent(event),
Torne (Richard Coles)a93a17c2013-05-15 11:34:50 +01001631 0));
1632 group->container->SetActive(false);
1633 group->suggested_button->SetState(state);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001634}
1635
1636void AutofillDialogViews::ContentsChanged(views::Textfield* sender,
Ben Murdoch558790d2013-07-30 15:19:42 +01001637 const base::string16& new_contents) {
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001638 TextfieldEditedOrActivated(sender, true);
1639}
1640
1641bool AutofillDialogViews::HandleKeyEvent(views::Textfield* sender,
1642 const ui::KeyEvent& key_event) {
1643 scoped_ptr<ui::KeyEvent> copy(key_event.Copy());
1644#if defined(OS_WIN) && !defined(USE_AURA)
1645 content::NativeWebKeyboardEvent event(copy->native_event());
1646#else
1647 content::NativeWebKeyboardEvent event(copy.get());
1648#endif
Ben Murdoch32409262013-08-07 11:04:47 +01001649 return delegate_->HandleKeyPressEventInInput(event);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001650}
1651
1652bool AutofillDialogViews::HandleMouseEvent(views::Textfield* sender,
1653 const ui::MouseEvent& mouse_event) {
Torne (Richard Coles)a93a17c2013-05-15 11:34:50 +01001654 if (mouse_event.IsLeftMouseButton() && sender->HasFocus()) {
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001655 TextfieldEditedOrActivated(sender, false);
Torne (Richard Coles)a93a17c2013-05-15 11:34:50 +01001656 // Show an error bubble if a user clicks on an input that's already focused
1657 // (and invalid).
1658 ShowErrorBubbleForViewIfNecessary(sender);
1659 }
1660
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001661 return false;
1662}
1663
1664void AutofillDialogViews::OnWillChangeFocus(
1665 views::View* focused_before,
1666 views::View* focused_now) {
Ben Murdoch32409262013-08-07 11:04:47 +01001667 delegate_->FocusMoved();
Torne (Richard Coles)a93a17c2013-05-15 11:34:50 +01001668 error_bubble_.reset();
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001669}
1670
1671void AutofillDialogViews::OnDidChangeFocus(
1672 views::View* focused_before,
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001673 views::View* focused_now) {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001674 // If user leaves an edit-field, revalidate the group it belongs to.
Torne (Richard Coles)a93a17c2013-05-15 11:34:50 +01001675 if (focused_before) {
1676 DetailsGroup* group = GroupForView(focused_before);
1677 if (group && group->container->visible())
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +01001678 ValidateGroup(*group, VALIDATE_EDIT);
Torne (Richard Coles)a93a17c2013-05-15 11:34:50 +01001679 }
1680
1681 // Show an error bubble when the user focuses the input.
1682 if (focused_now)
1683 ShowErrorBubbleForViewIfNecessary(focused_now);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001684}
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001685
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001686void AutofillDialogViews::OnSelectedIndexChanged(views::Combobox* combobox) {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001687 DetailsGroup* group = GroupForView(combobox);
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +01001688 ValidateGroup(*group, VALIDATE_EDIT);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001689}
1690
1691void AutofillDialogViews::StyledLabelLinkClicked(const ui::Range& range,
1692 int event_flags) {
Ben Murdoch32409262013-08-07 11:04:47 +01001693 delegate_->LegalDocumentLinkClicked(range);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001694}
1695
1696void AutofillDialogViews::InitChildViews() {
Ben Murdocheb525c52013-07-10 11:40:50 +01001697 button_strip_extra_view_ = new LayoutPropagationView();
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001698 button_strip_extra_view_->SetLayoutManager(
1699 new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, 0));
1700
Ben Murdochbb1529c2013-08-08 10:24:53 +01001701 save_in_chrome_checkbox_container_ = new views::View();
1702 save_in_chrome_checkbox_container_->SetLayoutManager(
1703 new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, 7));
1704 button_strip_extra_view_->AddChildView(save_in_chrome_checkbox_container_);
1705
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001706 save_in_chrome_checkbox_ =
Ben Murdoch32409262013-08-07 11:04:47 +01001707 new views::Checkbox(delegate_->SaveLocallyText());
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +01001708 save_in_chrome_checkbox_->SetChecked(true);
Ben Murdochbb1529c2013-08-08 10:24:53 +01001709 save_in_chrome_checkbox_container_->AddChildView(save_in_chrome_checkbox_);
1710
1711 save_in_chrome_checkbox_container_->AddChildView(
1712 new TooltipIcon(delegate_->SaveLocallyTooltip()));
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001713
Ben Murdoch2385ea32013-08-06 11:01:04 +01001714 button_strip_image_ = new views::ImageView();
1715 button_strip_extra_view_->AddChildView(button_strip_image_);
1716
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001717 autocheckout_progress_bar_view_ = new views::View();
Ben Murdoch7dbb3d52013-07-17 14:55:54 +01001718 views::GridLayout* progress_bar_layout =
1719 new views::GridLayout(autocheckout_progress_bar_view_);
1720 autocheckout_progress_bar_view_->SetLayoutManager(progress_bar_layout);
1721 const int kColumnSetId = 0;
1722 views::ColumnSet* columns = progress_bar_layout->AddColumnSet(kColumnSetId);
1723 columns->AddColumn(views::GridLayout::LEADING,
1724 views::GridLayout::CENTER,
1725 0,
1726 views::GridLayout::USE_PREF,
1727 0,
1728 0);
1729 progress_bar_layout->StartRow(1.0, kColumnSetId);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001730
1731 autocheckout_progress_bar_ = new AutocheckoutProgressBar();
Ben Murdoch7dbb3d52013-07-17 14:55:54 +01001732 progress_bar_layout->AddView(autocheckout_progress_bar_);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001733 button_strip_extra_view_->AddChildView(autocheckout_progress_bar_view_);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001734
Ben Murdoch32409262013-08-07 11:04:47 +01001735 account_chooser_ = new AccountChooser(delegate_);
1736 notification_area_ = new NotificationArea(delegate_);
Ben Murdocheb525c52013-07-10 11:40:50 +01001737 notification_area_->set_arrow_centering_anchor(account_chooser_->AsWeakPtr());
1738 AddChildView(notification_area_);
1739
1740 scrollable_area_ = new views::ScrollView();
1741 scrollable_area_->set_hide_horizontal_scrollbar(true);
1742 scrollable_area_->SetContents(CreateDetailsContainer());
1743 AddChildView(scrollable_area_);
1744
1745 autocheckout_steps_area_ = new AutocheckoutStepsArea();
1746 AddChildView(autocheckout_steps_area_);
1747
1748 loading_shield_ = new views::Label();
1749 loading_shield_->SetVisible(false);
1750 loading_shield_->set_background(views::Background::CreateSolidBackground(
1751 GetNativeTheme()->GetSystemColor(
1752 ui::NativeTheme::kColorId_DialogBackground)));
1753 loading_shield_->SetFont(ui::ResourceBundle::GetSharedInstance().GetFont(
1754 ui::ResourceBundle::BaseFont).DeriveFont(15));
1755 AddChildView(loading_shield_);
1756
Ben Murdoch32409262013-08-07 11:04:47 +01001757 sign_in_webview_ = new views::WebView(delegate_->profile());
Torne (Richard Coles)a93a17c2013-05-15 11:34:50 +01001758 sign_in_webview_->SetVisible(false);
Ben Murdocheb525c52013-07-10 11:40:50 +01001759 AddChildView(sign_in_webview_);
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +01001760 sign_in_delegate_.reset(
1761 new AutofillDialogSignInDelegate(this,
1762 sign_in_webview_->GetWebContents()));
Ben Murdoch7dbb3d52013-07-17 14:55:54 +01001763
1764 overlay_view_ = new OverlayView(this);
1765 overlay_view_->SetVisible(false);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001766}
1767
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001768views::View* AutofillDialogViews::CreateDetailsContainer() {
Torne (Richard Coles)a93a17c2013-05-15 11:34:50 +01001769 details_container_ = new DetailsContainerView(
1770 base::Bind(&AutofillDialogViews::DetailsContainerBoundsChanged,
1771 base::Unretained(this)));
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001772 // A box layout is used because it respects widget visibility.
1773 details_container_->SetLayoutManager(
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001774 new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0));
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001775 for (DetailGroupMap::iterator iter = detail_groups_.begin();
1776 iter != detail_groups_.end(); ++iter) {
1777 CreateDetailsSection(iter->second.section);
1778 details_container_->AddChildView(iter->second.container);
1779 }
1780
1781 return details_container_;
1782}
1783
1784void AutofillDialogViews::CreateDetailsSection(DialogSection section) {
1785 // Inputs container (manual inputs + combobox).
1786 views::View* inputs_container = CreateInputsContainer(section);
1787
1788 DetailsGroup* group = GroupForSection(section);
1789 // Container (holds label + inputs).
1790 group->container = new SectionContainer(
Ben Murdoch32409262013-08-07 11:04:47 +01001791 delegate_->LabelForSection(section),
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001792 inputs_container,
1793 group->suggested_button);
Ben Murdochbbcdd452013-07-25 10:06:34 +01001794 DCHECK(group->suggested_button->parent());
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001795 UpdateDetailsGroupState(*group);
1796}
1797
1798views::View* AutofillDialogViews::CreateInputsContainer(DialogSection section) {
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001799 // The |info_view| holds |manual_inputs| and |suggested_info|, allowing the
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001800 // dialog to toggle which is shown.
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001801 views::View* info_view = new views::View();
1802 info_view->SetLayoutManager(
1803 new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0));
1804
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001805 views::View* manual_inputs = InitInputsView(section);
1806 info_view->AddChildView(manual_inputs);
1807 SuggestionView* suggested_info =
Ben Murdoch32409262013-08-07 11:04:47 +01001808 new SuggestionView(delegate_->EditSuggestionText(), this);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001809 info_view->AddChildView(suggested_info);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001810
Ben Murdochbbcdd452013-07-25 10:06:34 +01001811 // TODO(estade): It might be slightly more OO if this button were created
1812 // and listened to by the section container.
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001813 views::ImageButton* menu_button = new views::ImageButton(this);
1814 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001815 menu_button->SetImage(views::Button::STATE_NORMAL,
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001816 rb.GetImageSkiaNamed(IDR_AUTOFILL_DIALOG_MENU_BUTTON));
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001817 menu_button->SetImage(views::Button::STATE_PRESSED,
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001818 rb.GetImageSkiaNamed(IDR_AUTOFILL_DIALOG_MENU_BUTTON_P));
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001819 menu_button->SetImage(views::Button::STATE_HOVERED,
1820 rb.GetImageSkiaNamed(IDR_AUTOFILL_DIALOG_MENU_BUTTON_H));
1821 menu_button->SetImage(views::Button::STATE_DISABLED,
1822 rb.GetImageSkiaNamed(IDR_AUTOFILL_DIALOG_MENU_BUTTON_D));
1823 menu_button->set_border(views::Border::CreateEmptyBorder(
Ben Murdochbbcdd452013-07-25 10:06:34 +01001824 kMenuButtonTopInset,
Ben Murdoch558790d2013-07-30 15:19:42 +01001825 kDialogEdgePadding,
Ben Murdochbbcdd452013-07-25 10:06:34 +01001826 kMenuButtonBottomInset,
1827 0));
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001828
1829 DetailsGroup* group = GroupForSection(section);
1830 group->suggested_button = menu_button;
1831 group->manual_input = manual_inputs;
1832 group->suggested_info = suggested_info;
Ben Murdochbbcdd452013-07-25 10:06:34 +01001833 return info_view;
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001834}
1835
1836// TODO(estade): we should be using Chrome-style constrained window padding
1837// values.
1838views::View* AutofillDialogViews::InitInputsView(DialogSection section) {
Ben Murdoch32409262013-08-07 11:04:47 +01001839 const DetailInputs& inputs = delegate_->RequestedFieldsForSection(section);
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001840 TextfieldMap* textfields = &GroupForSection(section)->textfields;
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001841 ComboboxMap* comboboxes = &GroupForSection(section)->comboboxes;
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001842
1843 views::View* view = new views::View();
1844 views::GridLayout* layout = new views::GridLayout(view);
1845 view->SetLayoutManager(layout);
1846
1847 for (DetailInputs::const_iterator it = inputs.begin();
1848 it != inputs.end(); ++it) {
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001849 const DetailInput& input = *it;
Ben Murdoch558790d2013-07-30 15:19:42 +01001850 ui::ComboboxModel* input_model =
Ben Murdoch32409262013-08-07 11:04:47 +01001851 delegate_->ComboboxModelForAutofillType(input.type);
Ben Murdoch558790d2013-07-30 15:19:42 +01001852 scoped_ptr<views::View> view_to_add;
1853 if (input_model) {
1854 views::Combobox* combobox = new views::Combobox(input_model);
1855 combobox->set_listener(this);
1856 comboboxes->insert(std::make_pair(&input, combobox));
1857
1858 for (int i = 0; i < input_model->GetItemCount(); ++i) {
1859 if (input.initial_value == input_model->GetItemAt(i)) {
1860 combobox->SetSelectedIndex(i);
1861 break;
1862 }
1863 }
1864
1865 view_to_add.reset(combobox);
1866 } else {
1867 DecoratedTextfield* field = new DecoratedTextfield(
1868 input.initial_value,
1869 l10n_util::GetStringUTF16(input.placeholder_text_rid),
1870 this);
1871
1872 gfx::Image icon =
Ben Murdoch32409262013-08-07 11:04:47 +01001873 delegate_->IconForField(input.type, input.initial_value);
Ben Murdoch558790d2013-07-30 15:19:42 +01001874 field->SetIcon(icon);
1875
1876 textfields->insert(std::make_pair(&input, field));
1877 view_to_add.reset(field);
1878 }
1879
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001880 int kColumnSetId = input.row_id;
Ben Murdoch558790d2013-07-30 15:19:42 +01001881 if (kColumnSetId < 0) {
1882 other_owned_views_.push_back(view_to_add.release());
1883 continue;
1884 }
1885
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001886 views::ColumnSet* column_set = layout->GetColumnSet(kColumnSetId);
1887 if (!column_set) {
1888 // Create a new column set and row.
1889 column_set = layout->AddColumnSet(kColumnSetId);
1890 if (it != inputs.begin())
1891 layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
1892 layout->StartRow(0, kColumnSetId);
1893 } else {
1894 // Add a new column to existing row.
1895 column_set->AddPaddingColumn(0, views::kRelatedControlHorizontalSpacing);
1896 // Must explicitly skip the padding column since we've already started
1897 // adding views.
1898 layout->SkipColumns(1);
1899 }
1900
1901 float expand = input.expand_weight;
1902 column_set->AddColumn(views::GridLayout::FILL,
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +01001903 views::GridLayout::FILL,
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +01001904 expand ? expand : 1.0,
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001905 views::GridLayout::USE_PREF,
1906 0,
1907 0);
1908
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +01001909 // This is the same as AddView(view_to_add), except that 1 is used for the
1910 // view's preferred width. Thus the width of the column completely depends
1911 // on |expand|.
Ben Murdoch558790d2013-07-30 15:19:42 +01001912 layout->AddView(view_to_add.release(), 1, 1,
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +01001913 views::GridLayout::FILL, views::GridLayout::FILL,
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +01001914 1, 0);
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001915 }
1916
1917 return view;
1918}
1919
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001920void AutofillDialogViews::UpdateSectionImpl(
1921 DialogSection section,
1922 bool clobber_inputs) {
1923 const DetailInputs& updated_inputs =
Ben Murdoch32409262013-08-07 11:04:47 +01001924 delegate_->RequestedFieldsForSection(section);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001925 DetailsGroup* group = GroupForSection(section);
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001926
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001927 for (DetailInputs::const_iterator iter = updated_inputs.begin();
1928 iter != updated_inputs.end(); ++iter) {
1929 const DetailInput& input = *iter;
1930 TextfieldMap::iterator text_mapping = group->textfields.find(&input);
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001931
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001932 if (text_mapping != group->textfields.end()) {
Ben Murdocheb525c52013-07-10 11:40:50 +01001933 DecoratedTextfield* decorated = text_mapping->second;
1934 decorated->SetEnabled(input.editable);
1935 if (decorated->text().empty() || clobber_inputs) {
1936 decorated->SetText(iter->initial_value);
1937 decorated->SetIcon(
Ben Murdoch32409262013-08-07 11:04:47 +01001938 delegate_->IconForField(input.type, decorated->text()));
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001939 }
1940 }
1941
1942 ComboboxMap::iterator combo_mapping = group->comboboxes.find(&input);
1943 if (combo_mapping != group->comboboxes.end()) {
1944 views::Combobox* combobox = combo_mapping->second;
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +01001945 combobox->SetEnabled(input.editable);
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +01001946 if (combobox->selected_index() == combobox->model()->GetDefaultIndex() ||
1947 clobber_inputs) {
1948 for (int i = 0; i < combobox->model()->GetItemCount(); ++i) {
1949 if (input.initial_value == combobox->model()->GetItemAt(i)) {
1950 combobox->SetSelectedIndex(i);
1951 break;
1952 }
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001953 }
1954 }
1955 }
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001956 }
1957
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001958 UpdateDetailsGroupState(*group);
1959}
1960
1961void AutofillDialogViews::UpdateDetailsGroupState(const DetailsGroup& group) {
1962 const SuggestionState& suggestion_state =
Ben Murdoch32409262013-08-07 11:04:47 +01001963 delegate_->SuggestionStateForSection(group.section);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001964 bool show_suggestions = !suggestion_state.text.empty();
1965 group.suggested_info->SetVisible(show_suggestions);
1966 group.suggested_info->SetSuggestionText(suggestion_state.text,
1967 suggestion_state.text_style);
1968 group.suggested_info->SetSuggestionIcon(suggestion_state.icon);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001969
1970 if (!suggestion_state.extra_text.empty()) {
1971 group.suggested_info->ShowTextfield(
1972 suggestion_state.extra_text,
Ben Murdocheb525c52013-07-10 11:40:50 +01001973 suggestion_state.extra_icon);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001974 }
1975
1976 group.manual_input->SetVisible(!show_suggestions);
1977
Ben Murdoch2385ea32013-08-06 11:01:04 +01001978 UpdateButtonStripExtraView();
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001979
Ben Murdoch32409262013-08-07 11:04:47 +01001980 const bool has_menu = !!delegate_->MenuModelForSection(group.section);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001981
1982 if (group.suggested_button)
1983 group.suggested_button->SetVisible(has_menu);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001984
1985 if (group.container) {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001986 group.container->SetForwardMouseEvents(has_menu && show_suggestions);
Ben Murdoch32409262013-08-07 11:04:47 +01001987 group.container->SetVisible(delegate_->SectionIsActive(group.section));
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001988 if (group.container->visible())
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +01001989 ValidateGroup(group, VALIDATE_EDIT);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001990 }
1991
1992 ContentsPreferredSizeChanged();
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001993}
1994
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001995template<class T>
1996void AutofillDialogViews::SetValidityForInput(
1997 T* input,
Ben Murdoch558790d2013-07-30 15:19:42 +01001998 const base::string16& message) {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001999 bool invalid = !message.empty();
2000 input->SetInvalid(invalid);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00002001
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01002002 if (invalid) {
2003 validity_map_[input] = message;
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01002004 } else {
2005 validity_map_.erase(input);
Torne (Richard Coles)a93a17c2013-05-15 11:34:50 +01002006
2007 if (error_bubble_ && error_bubble_->anchor() == input) {
2008 validity_map_.erase(input);
2009 error_bubble_.reset();
2010 }
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01002011 }
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00002012}
2013
Torne (Richard Coles)a93a17c2013-05-15 11:34:50 +01002014void AutofillDialogViews::ShowErrorBubbleForViewIfNecessary(views::View* view) {
2015 if (!view->GetWidget())
2016 return;
2017
Ben Murdocheb525c52013-07-10 11:40:50 +01002018 if (error_bubble_ && error_bubble_->anchor() == view)
Torne (Richard Coles)a93a17c2013-05-15 11:34:50 +01002019 return;
2020
Ben Murdoch558790d2013-07-30 15:19:42 +01002021 std::map<views::View*, base::string16>::iterator error_message =
Ben Murdocheb525c52013-07-10 11:40:50 +01002022 validity_map_.find(view);
Ben Murdochca12bfa2013-07-23 11:17:05 +01002023 if (error_message != validity_map_.end()) {
2024 view->ScrollRectToVisible(view->GetLocalBounds());
Ben Murdocheb525c52013-07-10 11:40:50 +01002025 error_bubble_.reset(new ErrorBubble(view, error_message->second));
Ben Murdochca12bfa2013-07-23 11:17:05 +01002026 }
Torne (Richard Coles)a93a17c2013-05-15 11:34:50 +01002027}
2028
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +01002029void AutofillDialogViews::MarkInputsInvalid(DialogSection section,
2030 const ValidityData& validity_data) {
Ben Murdocheb525c52013-07-10 11:40:50 +01002031 DetailsGroup* group = GroupForSection(section);
2032 DCHECK(group->container->visible());
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +01002033
Ben Murdoch32409262013-08-07 11:04:47 +01002034 typedef std::map<ServerFieldType,
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +01002035 base::Callback<void(const base::string16&)> > FieldMap;
2036 FieldMap field_map;
2037
Ben Murdocheb525c52013-07-10 11:40:50 +01002038 if (group->manual_input->visible()) {
2039 for (TextfieldMap::const_iterator iter = group->textfields.begin();
2040 iter != group->textfields.end(); ++iter) {
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +01002041 field_map[iter->first->type] = base::Bind(
2042 &AutofillDialogViews::SetValidityForInput<DecoratedTextfield>,
2043 base::Unretained(this),
2044 iter->second);
2045 }
Ben Murdocheb525c52013-07-10 11:40:50 +01002046 for (ComboboxMap::const_iterator iter = group->comboboxes.begin();
2047 iter != group->comboboxes.end(); ++iter) {
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +01002048 field_map[iter->first->type] = base::Bind(
2049 &AutofillDialogViews::SetValidityForInput<views::Combobox>,
2050 base::Unretained(this),
2051 iter->second);
2052 }
Ben Murdocheb525c52013-07-10 11:40:50 +01002053 } else {
2054 // Purge invisible views from |validity_map_|.
2055 std::map<views::View*, base::string16>::iterator it;
2056 for (it = validity_map_.begin(); it != validity_map_.end();) {
2057 DCHECK(GroupForView(it->first));
2058 if (GroupForView(it->first) == group)
2059 validity_map_.erase(it++);
2060 else
2061 ++it;
2062 }
2063
2064 if (section == SECTION_CC) {
2065 // Special case CVC as it's not part of |group->manual_input|.
2066 field_map[CREDIT_CARD_VERIFICATION_CODE] = base::Bind(
2067 &AutofillDialogViews::SetValidityForInput<DecoratedTextfield>,
2068 base::Unretained(this),
2069 group->suggested_info->decorated_textfield());
2070 }
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +01002071 }
2072
2073 // Flag invalid fields, removing them from |field_map|.
2074 for (ValidityData::const_iterator iter = validity_data.begin();
2075 iter != validity_data.end(); ++iter) {
Ben Murdoch558790d2013-07-30 15:19:42 +01002076 const base::string16& message = iter->second;
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +01002077 field_map[iter->first].Run(message);
2078 field_map.erase(iter->first);
2079 }
2080
2081 // The remaining fields in |field_map| are valid. Mark them as such.
2082 for (FieldMap::iterator iter = field_map.begin(); iter != field_map.end();
2083 ++iter) {
2084 iter->second.Run(base::string16());
2085 }
2086}
2087
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +01002088bool AutofillDialogViews::ValidateGroup(const DetailsGroup& group,
2089 ValidationType validation_type) {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01002090 DCHECK(group.container->visible());
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00002091
2092 scoped_ptr<DetailInput> cvc_input;
2093 DetailOutputMap detail_outputs;
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00002094
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01002095 if (group.manual_input->visible()) {
2096 for (TextfieldMap::const_iterator iter = group.textfields.begin();
2097 iter != group.textfields.end(); ++iter) {
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +01002098 if (!iter->first->editable)
2099 continue;
2100
Ben Murdocheb525c52013-07-10 11:40:50 +01002101 detail_outputs[iter->first] = iter->second->text();
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00002102 }
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01002103 for (ComboboxMap::const_iterator iter = group.comboboxes.begin();
2104 iter != group.comboboxes.end(); ++iter) {
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +01002105 if (!iter->first->editable)
2106 continue;
2107
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00002108 views::Combobox* combobox = iter->second;
Ben Murdoch558790d2013-07-30 15:19:42 +01002109 base::string16 item =
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00002110 combobox->model()->GetItemAt(combobox->selected_index());
2111 detail_outputs[iter->first] = item;
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00002112 }
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01002113 } else if (group.section == SECTION_CC) {
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00002114 DecoratedTextfield* decorated_cvc =
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01002115 group.suggested_info->decorated_textfield();
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00002116 cvc_input.reset(new DetailInput);
2117 cvc_input->type = CREDIT_CARD_VERIFICATION_CODE;
Ben Murdocheb525c52013-07-10 11:40:50 +01002118 detail_outputs[cvc_input.get()] = decorated_cvc->text();
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00002119 }
2120
Ben Murdoch32409262013-08-07 11:04:47 +01002121 ValidityData invalid_inputs = delegate_->InputsAreValid(
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +01002122 group.section, detail_outputs, validation_type);
2123 MarkInputsInvalid(group.section, invalid_inputs);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00002124
2125 return invalid_inputs.empty();
2126}
2127
2128bool AutofillDialogViews::ValidateForm() {
2129 bool all_valid = true;
Ben Murdocheb525c52013-07-10 11:40:50 +01002130 validity_map_.clear();
2131
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00002132 for (DetailGroupMap::iterator iter = detail_groups_.begin();
2133 iter != detail_groups_.end(); ++iter) {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01002134 const DetailsGroup& group = iter->second;
2135 if (!group.container->visible())
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00002136 continue;
2137
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +01002138 if (!ValidateGroup(group, VALIDATE_FINAL))
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01002139 all_valid = false;
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00002140 }
2141
2142 return all_valid;
2143}
2144
2145void AutofillDialogViews::TextfieldEditedOrActivated(
2146 views::Textfield* textfield,
2147 bool was_edit) {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01002148 DetailsGroup* group = GroupForView(textfield);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00002149 DCHECK(group);
2150
Ben Murdoch32409262013-08-07 11:04:47 +01002151 // Figure out the ServerFieldType this textfield represents.
2152 ServerFieldType type = UNKNOWN_TYPE;
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00002153 DecoratedTextfield* decorated = NULL;
2154
2155 // Look for the input in the manual inputs.
2156 for (TextfieldMap::const_iterator iter = group->textfields.begin();
2157 iter != group->textfields.end();
2158 ++iter) {
2159 decorated = iter->second;
Ben Murdocheb525c52013-07-10 11:40:50 +01002160 if (decorated == textfield) {
Ben Murdoch32409262013-08-07 11:04:47 +01002161 delegate_->UserEditedOrActivatedInput(group->section,
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +01002162 iter->first,
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00002163 GetWidget()->GetNativeView(),
2164 textfield->GetBoundsInScreen(),
2165 textfield->text(),
2166 was_edit);
2167 type = iter->first->type;
2168 break;
2169 }
2170 }
2171
Ben Murdocheb525c52013-07-10 11:40:50 +01002172 if (textfield == group->suggested_info->decorated_textfield()) {
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00002173 decorated = group->suggested_info->decorated_textfield();
2174 type = CREDIT_CARD_VERIFICATION_CODE;
2175 }
2176 DCHECK_NE(UNKNOWN_TYPE, type);
2177
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01002178 // If the field is marked as invalid, check if the text is now valid.
2179 // Many fields (i.e. CC#) are invalid for most of the duration of editing,
2180 // so flagging them as invalid prematurely is not helpful. However,
2181 // correcting a minor mistake (i.e. a wrong CC digit) should immediately
2182 // result in validation - positive user feedback.
2183 if (decorated->invalid() && was_edit) {
Torne (Richard Coles)a93a17c2013-05-15 11:34:50 +01002184 SetValidityForInput<DecoratedTextfield>(
2185 decorated,
Ben Murdoch32409262013-08-07 11:04:47 +01002186 delegate_->InputValidityMessage(group->section, type,
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +01002187 textfield->text()));
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01002188
2189 // If the field transitioned from invalid to valid, re-validate the group,
2190 // since inter-field checks become meaningful with valid fields.
2191 if (!decorated->invalid())
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +01002192 ValidateGroup(*group, VALIDATE_EDIT);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01002193 }
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00002194
Ben Murdoch32409262013-08-07 11:04:47 +01002195 gfx::Image icon = delegate_->IconForField(type, textfield->text());
Ben Murdocheb525c52013-07-10 11:40:50 +01002196 decorated->SetIcon(icon);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00002197}
2198
Ben Murdoch2385ea32013-08-06 11:01:04 +01002199void AutofillDialogViews::UpdateButtonStripExtraView() {
Ben Murdochbb1529c2013-08-08 10:24:53 +01002200 save_in_chrome_checkbox_container_->SetVisible(
Ben Murdoch32409262013-08-07 11:04:47 +01002201 delegate_->ShouldOfferToSaveInChrome());
Ben Murdoch2385ea32013-08-06 11:01:04 +01002202
Ben Murdoch32409262013-08-07 11:04:47 +01002203 gfx::Image image = delegate_->ButtonStripImage();
Ben Murdoch2385ea32013-08-06 11:01:04 +01002204 button_strip_image_->SetVisible(!image.IsEmpty());
2205 button_strip_image_->SetImage(image.AsImageSkia());
2206
2207 autocheckout_progress_bar_view_->SetVisible(
Ben Murdoch32409262013-08-07 11:04:47 +01002208 delegate_->ShouldShowProgressBar());
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +01002209}
2210
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00002211void AutofillDialogViews::ContentsPreferredSizeChanged() {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01002212 if (GetWidget()) {
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00002213 GetWidget()->SetSize(GetWidget()->non_client_view()->GetPreferredSize());
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01002214 // If the above line does not cause the dialog's size to change, |contents_|
2215 // may not be laid out. This will trigger a layout only if it's needed.
Ben Murdocheb525c52013-07-10 11:40:50 +01002216 SetBoundsRect(bounds());
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01002217 }
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00002218}
2219
2220AutofillDialogViews::DetailsGroup* AutofillDialogViews::GroupForSection(
2221 DialogSection section) {
2222 return &detail_groups_.find(section)->second;
2223}
2224
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01002225AutofillDialogViews::DetailsGroup* AutofillDialogViews::GroupForView(
Ben Murdocheb525c52013-07-10 11:40:50 +01002226 views::View* view) {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01002227 DCHECK(view);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01002228
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01002229 for (DetailGroupMap::iterator iter = detail_groups_.begin();
2230 iter != detail_groups_.end(); ++iter) {
2231 DetailsGroup* group = &iter->second;
Ben Murdocheb525c52013-07-10 11:40:50 +01002232 if (view->parent() == group->manual_input)
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01002233 return group;
2234
Ben Murdocheb525c52013-07-10 11:40:50 +01002235 views::View* decorated =
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +01002236 view->GetAncestorWithClassName(DecoratedTextfield::kViewClassName);
Ben Murdocheb525c52013-07-10 11:40:50 +01002237
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01002238 // Textfields need to check a second case, since they can be
2239 // suggested inputs instead of directly editable inputs. Those are
2240 // accessed via |suggested_info|.
Ben Murdocheb525c52013-07-10 11:40:50 +01002241 if (decorated &&
2242 decorated == group->suggested_info->decorated_textfield()) {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01002243 return group;
2244 }
2245 }
2246 return NULL;
2247}
2248
2249views::Textfield* AutofillDialogViews::TextfieldForInput(
2250 const DetailInput& input) {
2251 for (DetailGroupMap::iterator iter = detail_groups_.begin();
2252 iter != detail_groups_.end(); ++iter) {
2253 const DetailsGroup& group = iter->second;
2254 TextfieldMap::const_iterator text_mapping = group.textfields.find(&input);
2255 if (text_mapping != group.textfields.end())
Ben Murdocheb525c52013-07-10 11:40:50 +01002256 return text_mapping->second;
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01002257 }
2258
2259 return NULL;
2260}
2261
2262views::Combobox* AutofillDialogViews::ComboboxForInput(
2263 const DetailInput& input) {
2264 for (DetailGroupMap::iterator iter = detail_groups_.begin();
2265 iter != detail_groups_.end(); ++iter) {
2266 const DetailsGroup& group = iter->second;
2267 ComboboxMap::const_iterator combo_mapping = group.comboboxes.find(&input);
2268 if (combo_mapping != group.comboboxes.end())
2269 return combo_mapping->second;
2270 }
2271
2272 return NULL;
2273}
2274
Torne (Richard Coles)a93a17c2013-05-15 11:34:50 +01002275void AutofillDialogViews::DetailsContainerBoundsChanged() {
2276 if (error_bubble_)
2277 error_bubble_->UpdatePosition();
2278}
2279
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00002280AutofillDialogViews::DetailsGroup::DetailsGroup(DialogSection section)
2281 : section(section),
2282 container(NULL),
2283 manual_input(NULL),
2284 suggested_info(NULL),
2285 suggested_button(NULL) {}
Torne (Richard Coles)58218062012-11-14 11:43:16 +00002286
2287AutofillDialogViews::DetailsGroup::~DetailsGroup() {}
2288
2289} // namespace autofill