blob: 2a8055eb057ff1e4b7f912a57b4c575359960ec4 [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)58218062012-11-14 11:43:16 +000012#include "chrome/browser/ui/autofill/autofill_dialog_controller.h"
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +010013#include "chrome/browser/ui/autofill/autofill_dialog_sign_in_delegate.h"
Torne (Richard Coles)58218062012-11-14 11:43:16 +000014#include "chrome/browser/ui/views/constrained_window_views.h"
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +010015#include "components/autofill/browser/autofill_type.h"
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +010016#include "components/autofill/content/browser/wallet/wallet_service_url.h"
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +010017#include "components/web_modal/web_contents_modal_dialog_manager.h"
18#include "components/web_modal/web_contents_modal_dialog_manager_delegate.h"
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000019#include "content/public/browser/native_web_keyboard_event.h"
20#include "content/public/browser/navigation_controller.h"
21#include "content/public/browser/web_contents.h"
22#include "content/public/browser/web_contents_view.h"
23#include "grit/theme_resources.h"
24#include "grit/ui_resources.h"
25#include "third_party/skia/include/core/SkColor.h"
26#include "ui/base/l10n/l10n_util.h"
27#include "ui/base/models/combobox_model.h"
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010028#include "ui/base/models/menu_model.h"
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000029#include "ui/base/resource/resource_bundle.h"
30#include "ui/gfx/canvas.h"
31#include "ui/views/background.h"
Torne (Richard Coles)58218062012-11-14 11:43:16 +000032#include "ui/views/border.h"
33#include "ui/views/controls/button/checkbox.h"
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000034#include "ui/views/controls/button/image_button.h"
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010035#include "ui/views/controls/button/label_button.h"
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +010036#include "ui/views/controls/button/label_button_border.h"
Torne (Richard Coles)58218062012-11-14 11:43:16 +000037#include "ui/views/controls/combobox/combobox.h"
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000038#include "ui/views/controls/image_view.h"
Torne (Richard Coles)58218062012-11-14 11:43:16 +000039#include "ui/views/controls/label.h"
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000040#include "ui/views/controls/link.h"
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000041#include "ui/views/controls/menu/menu_runner.h"
Torne (Richard Coles)58218062012-11-14 11:43:16 +000042#include "ui/views/controls/separator.h"
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010043#include "ui/views/controls/styled_label.h"
Torne (Richard Coles)58218062012-11-14 11:43:16 +000044#include "ui/views/controls/textfield/textfield.h"
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000045#include "ui/views/controls/webview/webview.h"
Torne (Richard Coles)58218062012-11-14 11:43:16 +000046#include "ui/views/layout/box_layout.h"
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000047#include "ui/views/layout/fill_layout.h"
Torne (Richard Coles)58218062012-11-14 11:43:16 +000048#include "ui/views/layout/grid_layout.h"
49#include "ui/views/layout/layout_constants.h"
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000050#include "ui/views/widget/widget.h"
51#include "ui/views/window/dialog_client_view.h"
Torne (Richard Coles)58218062012-11-14 11:43:16 +000052
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +010053using web_modal::WebContentsModalDialogManager;
54
Torne (Richard Coles)58218062012-11-14 11:43:16 +000055namespace autofill {
56
57namespace {
58
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010059// The minimum useful height of the contents area of the dialog.
60const int kMinimumContentsHeight = 100;
61
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000062// Horizontal padding between text and other elements (in pixels).
63const int kAroundTextPadding = 4;
64
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010065// Size of the triangular mark that indicates an invalid textfield (in pixels).
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000066const size_t kDogEarSize = 10;
67
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010068// The space between the edges of a notification bar and the text within (in
69// pixels).
70const size_t kNotificationPadding = 14;
71
72// Vertical padding above and below each detail section (in pixels).
73const size_t kDetailSectionInset = 10;
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000074
75const size_t kAutocheckoutProgressBarWidth = 300;
76const size_t kAutocheckoutProgressBarHeight = 11;
77
78const size_t kArrowHeight = 7;
79const size_t kArrowWidth = 2 * kArrowHeight;
80
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010081// The padding around the edges of the legal documents text, in pixels.
82const size_t kLegalDocPadding = 20;
83
84// Slight shading for mouse hover and legal document background.
85SkColor kShadingColor = SkColorSetARGB(7, 0, 0, 0);
86
87// A border color for the legal document view.
88SkColor kSubtleBorderColor = SkColorSetARGB(10, 0, 0, 0);
89
90// The top padding, in pixels, for the suggestions menu dropdown arrows.
91const size_t kMenuButtonTopOffset = 5;
92
93// The side padding, in pixels, for the suggestions menu dropdown arrows.
94const size_t kMenuButtonHorizontalPadding = 20;
95
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000096const char kDecoratedTextfieldClassName[] = "autofill/DecoratedTextfield";
97const char kNotificationAreaClassName[] = "autofill/NotificationArea";
98
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010099views::Border* CreateLabelAlignmentBorder() {
100 // TODO(estade): this should be made to match the native textfield top
101 // inset. It's hard to get at, so for now it's hard-coded.
102 return views::Border::CreateEmptyBorder(4, 0, 0, 0);
103}
104
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000105// Returns a label that describes a details section.
106views::Label* CreateDetailsSectionLabel(const string16& text) {
107 views::Label* label = new views::Label(text);
108 label->SetHorizontalAlignment(gfx::ALIGN_RIGHT);
109 label->SetFont(label->font().DeriveFont(0, gfx::Font::BOLD));
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100110 label->set_border(CreateLabelAlignmentBorder());
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000111 return label;
112}
113
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100114// Draws an arrow at the top of |canvas| pointing to |tip_x|.
115void DrawArrow(gfx::Canvas* canvas, int tip_x, const SkColor& color) {
116 const int arrow_half_width = kArrowWidth / 2.0f;
117 const int arrow_middle = tip_x - arrow_half_width;
118
119 SkPath arrow;
120 arrow.moveTo(arrow_middle - arrow_half_width, kArrowHeight);
121 arrow.lineTo(arrow_middle + arrow_half_width, kArrowHeight);
122 arrow.lineTo(arrow_middle, 0);
123 arrow.close();
124 canvas->ClipPath(arrow);
125 canvas->DrawColor(color);
126}
127
128// This class handles layout for the first row of a SuggestionView.
129// It exists to circumvent shortcomings of GridLayout and BoxLayout (namely that
130// the former doesn't fully respect child visibility, and that the latter won't
131// expand a single child).
132class SectionRowView : public views::View {
133 public:
134 SectionRowView() {}
135 virtual ~SectionRowView() {}
136
137 // views::View implementation:
138 virtual gfx::Size GetPreferredSize() OVERRIDE {
139 // Only the height matters anyway.
140 return child_at(2)->GetPreferredSize();
141 }
142
143 virtual void Layout() OVERRIDE {
144 const gfx::Rect bounds = GetContentsBounds();
145
146 // Icon is left aligned.
147 int start_x = bounds.x();
148 views::View* icon = child_at(0);
149 if (icon->visible()) {
150 icon->SizeToPreferredSize();
151 icon->SetX(start_x);
152 icon->SetY(bounds.y() +
153 (bounds.height() - icon->bounds().height()) / 2);
154 start_x += icon->bounds().width() + kAroundTextPadding;
155 }
156
157 // Textfield is right aligned.
158 int end_x = bounds.width();
159 views::View* decorated = child_at(2);
160 if (decorated->visible()) {
161 decorated->SizeToPreferredSize();
162 decorated->SetX(bounds.width() - decorated->bounds().width());
163 decorated->SetY(bounds.y());
164 end_x = decorated->bounds().x() - kAroundTextPadding;
165 }
166
167 // Label takes up all the space in between.
168 views::View* label = child_at(1);
169 if (label->visible())
170 label->SetBounds(start_x, bounds.y(), end_x - start_x, bounds.height());
171
172 views::View::Layout();
173 }
174
175 private:
176 DISALLOW_COPY_AND_ASSIGN(SectionRowView);
177};
178
179// This view is used for the contents of the error bubble widget.
180class ErrorBubbleContents : public views::View {
181 public:
182 explicit ErrorBubbleContents(const string16& message)
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +0100183 : color_(kWarningColor) {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100184 set_border(views::Border::CreateEmptyBorder(kArrowHeight, 0, 0, 0));
185
186 views::Label* label = new views::Label();
187 label->SetText(message);
188 label->SetAutoColorReadabilityEnabled(false);
189 label->SetEnabledColor(SK_ColorWHITE);
190 label->set_border(
191 views::Border::CreateSolidSidedBorder(5, 10, 5, 10, color_));
192 label->set_background(
193 views::Background::CreateSolidBackground(color_));
194 SetLayoutManager(new views::FillLayout());
195 AddChildView(label);
196 }
197 virtual ~ErrorBubbleContents() {}
198
199 virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE {
200 views::View::OnPaint(canvas);
201 DrawArrow(canvas, width() / 2.0f, color_);
202 }
203
204 private:
205 SkColor color_;
206
207 DISALLOW_COPY_AND_ASSIGN(ErrorBubbleContents);
208};
209
Torne (Richard Coles)a93a17c2013-05-15 11:34:50 +0100210// A view that runs a callback whenever its bounds change.
211class DetailsContainerView : public views::View {
212 public:
213 explicit DetailsContainerView(const base::Closure& callback)
214 : bounds_changed_callback_(callback) {}
215 virtual ~DetailsContainerView() {}
216
217 // views::View implementation.
218 virtual void OnBoundsChanged(const gfx::Rect& previous_bounds) OVERRIDE {
219 bounds_changed_callback_.Run();
220 }
221
222 private:
223 base::Closure bounds_changed_callback_;
224
225 DISALLOW_COPY_AND_ASSIGN(DetailsContainerView);
226};
227
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +0100228// ButtonStripView wraps the Autocheckout progress bar and the "[X] Save details
229// in Chrome" checkbox and listens for visibility changes.
230class ButtonStripView : public views::View {
231 public:
232 ButtonStripView() {}
233 virtual ~ButtonStripView() {}
234
235 protected:
236 virtual void ChildVisibilityChanged(views::View* child) OVERRIDE {
237 PreferredSizeChanged();
238 }
239
240 private:
241 DISALLOW_COPY_AND_ASSIGN(ButtonStripView);
242};
243
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000244} // namespace
245
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100246// AutofillDialogViews::SizeLimitedScrollView ----------------------------------
247
248AutofillDialogViews::SizeLimitedScrollView::SizeLimitedScrollView(
249 views::View* scroll_contents)
250 : max_height_(-1) {
251 set_hide_horizontal_scrollbar(true);
252 SetContents(scroll_contents);
253}
254
255AutofillDialogViews::SizeLimitedScrollView::~SizeLimitedScrollView() {}
256
257void AutofillDialogViews::SizeLimitedScrollView::Layout() {
258 contents()->SizeToPreferredSize();
259 ScrollView::Layout();
260}
261
262gfx::Size AutofillDialogViews::SizeLimitedScrollView::GetPreferredSize() {
263 gfx::Size size = contents()->GetPreferredSize();
264 if (max_height_ >= 0 && max_height_ < size.height())
265 size.set_height(max_height_);
266
267 return size;
268}
269
270void AutofillDialogViews::SizeLimitedScrollView::SetMaximumHeight(
271 int max_height) {
272 int old_max = max_height_;
273 max_height_ = max_height;
274
275 if (max_height_ < height() || old_max <= height())
276 PreferredSizeChanged();
277}
278
279// AutofillDialogViews::ErrorBubble --------------------------------------------
280
281AutofillDialogViews::ErrorBubble::ErrorBubble(views::View* anchor,
282 const string16& message)
283 : anchor_(anchor),
Torne (Richard Coles)a93a17c2013-05-15 11:34:50 +0100284 contents_(new ErrorBubbleContents(message)),
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100285 observer_(this) {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100286 widget_ = new views::Widget;
287 views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP);
288 params.transparent = true;
289 views::Widget* anchor_widget = anchor->GetWidget();
Torne (Richard Coles)a93a17c2013-05-15 11:34:50 +0100290 DCHECK(anchor_widget);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100291 params.parent = anchor_widget->GetNativeView();
292
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100293 widget_->Init(params);
Torne (Richard Coles)a93a17c2013-05-15 11:34:50 +0100294 widget_->SetContentsView(contents_);
295 UpdatePosition();
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100296 observer_.Add(widget_);
297}
298
299AutofillDialogViews::ErrorBubble::~ErrorBubble() {
300 if (widget_)
Torne (Richard Coles)a93a17c2013-05-15 11:34:50 +0100301 widget_->Close();
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100302}
303
304bool AutofillDialogViews::ErrorBubble::IsShowing() {
305 return widget_ && widget_->IsVisible();
306}
307
Torne (Richard Coles)a93a17c2013-05-15 11:34:50 +0100308void AutofillDialogViews::ErrorBubble::UpdatePosition() {
309 if (!widget_)
310 return;
311
312 if (!anchor_->GetVisibleBounds().IsEmpty()) {
313 widget_->SetBounds(GetBoundsForWidget());
314 widget_->SetVisibilityChangedAnimationsEnabled(true);
315 widget_->Show();
316 } else {
317 widget_->SetVisibilityChangedAnimationsEnabled(false);
318 widget_->Hide();
319 }
320}
321
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100322void AutofillDialogViews::ErrorBubble::OnWidgetClosing(views::Widget* widget) {
323 DCHECK_EQ(widget_, widget);
324 observer_.Remove(widget_);
325 widget_ = NULL;
326}
327
Torne (Richard Coles)a93a17c2013-05-15 11:34:50 +0100328gfx::Rect AutofillDialogViews::ErrorBubble::GetBoundsForWidget() {
329 gfx::Rect anchor_bounds = anchor_->GetBoundsInScreen();
330 gfx::Rect bubble_bounds;
331 bubble_bounds.set_size(contents_->GetPreferredSize());
332 bubble_bounds.set_x(anchor_bounds.right() -
333 (anchor_bounds.width() + bubble_bounds.width()) / 2);
334 const int kErrorBubbleOverlap = 3;
335 bubble_bounds.set_y(anchor_bounds.bottom() - kErrorBubbleOverlap);
336
337 return bubble_bounds;
338}
339
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000340// AutofillDialogViews::DecoratedTextfield -------------------------------------
341
342AutofillDialogViews::DecoratedTextfield::DecoratedTextfield(
343 const string16& default_value,
344 const string16& placeholder,
345 views::TextfieldController* controller)
346 : textfield_(new views::Textfield()),
347 invalid_(false) {
348 textfield_->set_placeholder_text(placeholder);
349 textfield_->SetText(default_value);
350 textfield_->SetController(controller);
351 SetLayoutManager(new views::FillLayout());
352 AddChildView(textfield_);
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000353}
354
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000355AutofillDialogViews::DecoratedTextfield::~DecoratedTextfield() {}
356
357void AutofillDialogViews::DecoratedTextfield::SetInvalid(bool invalid) {
358 invalid_ = invalid;
359 if (invalid)
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +0100360 textfield_->SetBorderColor(kWarningColor);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000361 else
362 textfield_->UseDefaultBorderColor();
363 SchedulePaint();
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000364}
365
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +0100366const char* AutofillDialogViews::DecoratedTextfield::GetClassName() const {
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000367 return kDecoratedTextfieldClassName;
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000368}
369
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000370void AutofillDialogViews::DecoratedTextfield::PaintChildren(
371 gfx::Canvas* canvas) {}
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000372
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000373void AutofillDialogViews::DecoratedTextfield::OnPaint(gfx::Canvas* canvas) {
374 // Draw the textfield first.
375 canvas->Save();
376 if (FlipCanvasOnPaintForRTLUI()) {
377 canvas->Translate(gfx::Vector2d(width(), 0));
378 canvas->Scale(-1, 1);
379 }
380 views::View::PaintChildren(canvas);
381 canvas->Restore();
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000382
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000383 // Then draw extra stuff on top.
384 if (invalid_) {
385 SkPath dog_ear;
386 dog_ear.moveTo(width() - kDogEarSize, 0);
387 dog_ear.lineTo(width(), 0);
388 dog_ear.lineTo(width(), kDogEarSize);
389 dog_ear.close();
390 canvas->ClipPath(dog_ear);
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +0100391 canvas->DrawColor(kWarningColor);
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000392 }
393}
394
Torne (Richard Coles)a93a17c2013-05-15 11:34:50 +0100395void AutofillDialogViews::DecoratedTextfield::RequestFocus() {
396 textfield()->RequestFocus();
397}
398
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000399// AutofillDialogViews::AccountChooser -----------------------------------------
400
401AutofillDialogViews::AccountChooser::AccountChooser(
402 AutofillDialogController* controller)
403 : image_(new views::ImageView()),
404 label_(new views::Label()),
405 arrow_(new views::ImageView()),
Torne (Richard Coles)a93a17c2013-05-15 11:34:50 +0100406 link_(new views::Link()),
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000407 controller_(controller) {
408 SetLayoutManager(
409 new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0,
410 kAroundTextPadding));
411 AddChildView(image_);
412 AddChildView(label_);
413
414 arrow_->SetImage(ui::ResourceBundle::GetSharedInstance().GetImageNamed(
415 IDR_MENU_DROPARROW).ToImageSkia());
416 AddChildView(arrow_);
417
418 link_->set_listener(this);
419 AddChildView(link_);
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000420}
421
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000422AutofillDialogViews::AccountChooser::~AccountChooser() {}
423
424void AutofillDialogViews::AccountChooser::Update() {
Torne (Richard Coles)a93a17c2013-05-15 11:34:50 +0100425 gfx::Image icon = controller_->AccountChooserImage();
426 image_->SetImage(icon.AsImageSkia());
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000427 label_->SetText(controller_->AccountChooserText());
428
429 bool show_link = !controller_->MenuModelForAccountChooser();
430 label_->SetVisible(!show_link);
431 arrow_->SetVisible(!show_link);
Torne (Richard Coles)a93a17c2013-05-15 11:34:50 +0100432 link_->SetText(controller_->SignInLinkText());
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000433 link_->SetVisible(show_link);
434
435 menu_runner_.reset();
436
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100437 PreferredSizeChanged();
438}
439
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000440bool AutofillDialogViews::AccountChooser::OnMousePressed(
441 const ui::MouseEvent& event) {
442 // Return true so we get the release event.
443 if (controller_->MenuModelForAccountChooser())
444 return event.IsOnlyLeftMouseButton();
445
446 return false;
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000447}
448
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000449void AutofillDialogViews::AccountChooser::OnMouseReleased(
450 const ui::MouseEvent& event) {
451 if (!HitTestPoint(event.location()))
452 return;
453
454 ui::MenuModel* model = controller_->MenuModelForAccountChooser();
455 if (!model)
456 return;
457
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100458 menu_runner_.reset(new views::MenuRunner(model));
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000459 ignore_result(
460 menu_runner_->RunMenuAt(GetWidget(),
461 NULL,
462 GetBoundsInScreen(),
463 views::MenuItemView::TOPRIGHT,
464 0));
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000465}
466
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000467void AutofillDialogViews::AccountChooser::LinkClicked(views::Link* source,
468 int event_flags) {
Torne (Richard Coles)a93a17c2013-05-15 11:34:50 +0100469 controller_->SignInLinkClicked();
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000470}
471
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000472// AutofillDialogViews::NotificationArea ---------------------------------------
473
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100474AutofillDialogViews::NotificationArea::NotificationArea(
475 AutofillDialogController* controller)
476 : controller_(controller),
477 checkbox_(NULL) {
478 // Reserve vertical space for the arrow (regardless of whether one exists).
479 set_border(views::Border::CreateEmptyBorder(kArrowHeight, 0, 0, 0));
480
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000481 views::BoxLayout* box_layout =
482 new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0);
483 SetLayoutManager(box_layout);
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000484}
485
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000486AutofillDialogViews::NotificationArea::~NotificationArea() {}
487
488void AutofillDialogViews::NotificationArea::SetNotifications(
489 const std::vector<DialogNotification>& notifications) {
490 notifications_ = notifications;
491
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000492 RemoveAllChildViews(true);
493 checkbox_ = NULL;
494
495 if (notifications_.empty())
496 return;
497
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000498 for (size_t i = 0; i < notifications_.size(); ++i) {
499 const DialogNotification& notification = notifications_[i];
500
501 scoped_ptr<views::View> view;
502 if (notification.HasCheckbox()) {
503 scoped_ptr<views::Checkbox> checkbox(new views::Checkbox(string16()));
504 checkbox_ = checkbox.get();
505 // We have to do this instead of using set_border() because a border
506 // is being used to draw the check square.
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100507 static_cast<views::LabelButtonBorder*>(checkbox->border())->
508 set_insets(gfx::Insets(kNotificationPadding,
509 kNotificationPadding,
510 kNotificationPadding,
511 kNotificationPadding));
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100512 if (!notification.interactive())
513 checkbox->SetState(views::Button::STATE_DISABLED);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000514 checkbox->SetText(notification.display_text());
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100515 checkbox->SetTextMultiLine(true);
516 checkbox->SetHorizontalAlignment(gfx::ALIGN_LEFT);
517 checkbox->SetTextColor(views::Button::STATE_NORMAL,
518 notification.GetTextColor());
519 checkbox->SetTextColor(views::Button::STATE_HOVERED,
520 notification.GetTextColor());
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100521 checkbox->SetChecked(notification.checked());
522 checkbox->set_listener(this);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000523 view.reset(checkbox.release());
524 } else {
525 scoped_ptr<views::Label> label(new views::Label());
526 label->SetText(notification.display_text());
527 label->SetMultiLine(true);
528 label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
529 label->SetAutoColorReadabilityEnabled(false);
530 label->SetEnabledColor(notification.GetTextColor());
531 label->set_border(views::Border::CreateSolidBorder(
532 kNotificationPadding, notification.GetBackgroundColor()));
533 view.reset(label.release());
534 }
535
536 view->set_background(views::Background::CreateSolidBackground(
537 notification.GetBackgroundColor()));
538 AddChildView(view.release());
539 }
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000540
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100541 PreferredSizeChanged();
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000542}
543
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100544gfx::Size AutofillDialogViews::NotificationArea::GetPreferredSize() {
545 gfx::Size size = views::View::GetPreferredSize();
546 // Ensure that long notifications wrap and don't enlarge the dialog.
547 size.set_width(1);
548 return size;
549}
550
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +0100551const char* AutofillDialogViews::NotificationArea::GetClassName() const {
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000552 return kNotificationAreaClassName;
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000553}
554
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000555void AutofillDialogViews::NotificationArea::OnPaint(gfx::Canvas* canvas) {
556 views::View::OnPaint(canvas);
557
558 if (HasArrow()) {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100559 DrawArrow(
560 canvas,
561 GetMirroredXInView(width() - arrow_centering_anchor_->width() / 2.0f),
562 notifications_[0].GetBackgroundColor());
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000563 }
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000564}
565
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100566void AutofillDialogViews::OnWidgetClosing(views::Widget* widget) {
567 observer_.Remove(widget);
568}
569
570void AutofillDialogViews::OnWidgetBoundsChanged(views::Widget* widget,
571 const gfx::Rect& new_bounds) {
572 int non_scrollable_height = window_->GetContentsView()->bounds().height() -
573 scrollable_area_->bounds().height();
574 int browser_window_height = widget->GetContentsView()->bounds().height();
575
576 scrollable_area_->SetMaximumHeight(
577 std::max(kMinimumContentsHeight,
578 (browser_window_height - non_scrollable_height) * 8 / 10));
579 ContentsPreferredSizeChanged();
580}
581
582void AutofillDialogViews::NotificationArea::ButtonPressed(
583 views::Button* sender, const ui::Event& event) {
584 DCHECK_EQ(sender, checkbox_);
585 controller_->NotificationCheckboxStateChanged(notifications_.front().type(),
586 checkbox_->checked());
587}
588
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000589bool AutofillDialogViews::NotificationArea::HasArrow() {
590 return !notifications_.empty() && notifications_[0].HasArrow() &&
591 arrow_centering_anchor_.get();
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000592}
593
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000594// AutofillDialogViews::SectionContainer ---------------------------------------
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000595
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000596AutofillDialogViews::SectionContainer::SectionContainer(
597 const string16& label,
598 views::View* controls,
599 views::Button* proxy_button)
600 : proxy_button_(proxy_button),
601 forward_mouse_events_(false) {
602 set_notify_enter_exit_on_child(true);
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000603
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000604 views::GridLayout* layout = new views::GridLayout(this);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100605 layout->SetInsets(kDetailSectionInset, 0, kDetailSectionInset, 0);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000606 SetLayoutManager(layout);
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000607
608 const int kColumnSetId = 0;
609 views::ColumnSet* column_set = layout->AddColumnSet(kColumnSetId);
610 // TODO(estade): pull out these constants, and figure out better values
611 // for them.
612 column_set->AddColumn(views::GridLayout::FILL,
613 views::GridLayout::LEADING,
614 0,
615 views::GridLayout::FIXED,
616 180,
617 0);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100618 column_set->AddPaddingColumn(0, 30);
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000619 column_set->AddColumn(views::GridLayout::FILL,
620 views::GridLayout::LEADING,
621 0,
622 views::GridLayout::FIXED,
623 300,
624 0);
625
626 layout->StartRow(0, kColumnSetId);
627 layout->AddView(CreateDetailsSectionLabel(label));
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000628 layout->AddView(controls);
629}
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000630
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000631AutofillDialogViews::SectionContainer::~SectionContainer() {}
632
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100633void AutofillDialogViews::SectionContainer::SetActive(bool active) {
634 bool is_active = active && proxy_button_->visible();
635 if (is_active == !!background())
636 return;
637
638 set_background(is_active ?
639 views::Background::CreateSolidBackground(kShadingColor) :
640 NULL);
641 SchedulePaint();
642}
643
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000644void AutofillDialogViews::SectionContainer::SetForwardMouseEvents(
645 bool forward) {
646 forward_mouse_events_ = forward;
647 if (!forward)
648 set_background(NULL);
649}
650
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000651void AutofillDialogViews::SectionContainer::OnMouseMoved(
652 const ui::MouseEvent& event) {
653 if (!forward_mouse_events_)
654 return;
655
656 SetActive(true);
657}
658
659void AutofillDialogViews::SectionContainer::OnMouseEntered(
660 const ui::MouseEvent& event) {
661 if (!forward_mouse_events_)
662 return;
663
664 SetActive(true);
665 proxy_button_->OnMouseEntered(ProxyEvent(event));
666 SchedulePaint();
667}
668
669void AutofillDialogViews::SectionContainer::OnMouseExited(
670 const ui::MouseEvent& event) {
671 if (!forward_mouse_events_)
672 return;
673
674 SetActive(false);
675 proxy_button_->OnMouseExited(ProxyEvent(event));
676 SchedulePaint();
677}
678
679bool AutofillDialogViews::SectionContainer::OnMousePressed(
680 const ui::MouseEvent& event) {
681 if (!forward_mouse_events_)
682 return false;
683
684 return proxy_button_->OnMousePressed(ProxyEvent(event));
685}
686
687void AutofillDialogViews::SectionContainer::OnMouseReleased(
688 const ui::MouseEvent& event) {
689 if (!forward_mouse_events_)
690 return;
691
692 proxy_button_->OnMouseReleased(ProxyEvent(event));
693}
694
695// static
696ui::MouseEvent AutofillDialogViews::SectionContainer::ProxyEvent(
697 const ui::MouseEvent& event) {
698 ui::MouseEvent event_copy = event;
699 event_copy.set_location(gfx::Point());
700 return event_copy;
701}
702
703// AutofilDialogViews::SuggestionView ------------------------------------------
704
705AutofillDialogViews::SuggestionView::SuggestionView(
706 const string16& edit_label,
707 AutofillDialogViews* autofill_dialog)
708 : label_(new views::Label()),
709 label_line_2_(new views::Label()),
710 icon_(new views::ImageView()),
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100711 label_container_(new SectionRowView()),
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000712 decorated_(
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100713 new DecoratedTextfield(string16(), string16(), autofill_dialog)),
714 edit_link_(new views::Link(edit_label)) {
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000715 // Label and icon.
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100716 label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
717 label_->set_border(CreateLabelAlignmentBorder());
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000718 label_container_->AddChildView(icon_);
719 label_container_->AddChildView(label_);
720 label_container_->AddChildView(decorated_);
721 decorated_->SetVisible(false);
722 // TODO(estade): get the sizing and spacing right on this textfield.
723 decorated_->textfield()->set_default_width_in_chars(10);
724 AddChildView(label_container_);
725
726 label_line_2_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
727 label_line_2_->SetVisible(false);
728 AddChildView(label_line_2_);
729
730 // TODO(estade): The link needs to have a different color when hovered.
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100731 edit_link_->set_listener(autofill_dialog);
732 edit_link_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
733 edit_link_->SetUnderline(false);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000734
735 // This container prevents the edit link from being horizontally stretched.
736 views::View* link_container = new views::View();
737 link_container->SetLayoutManager(
738 new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, 0));
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100739 link_container->AddChildView(edit_link_);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000740 AddChildView(link_container);
741
742 SetLayoutManager(new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0));
743}
744
745AutofillDialogViews::SuggestionView::~SuggestionView() {}
746
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100747void AutofillDialogViews::SuggestionView::SetEditable(bool editable) {
748 edit_link_->SetVisible(editable);
749}
750
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000751void AutofillDialogViews::SuggestionView::SetSuggestionText(
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100752 const string16& text,
753 gfx::Font::FontStyle text_style) {
754 label_->SetFont(ui::ResourceBundle::GetSharedInstance().GetFont(
755 ui::ResourceBundle::BaseFont).DeriveFont(0, text_style));
756
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000757 // TODO(estade): does this localize well?
758 string16 line_return(ASCIIToUTF16("\n"));
759 size_t position = text.find(line_return);
760 if (position == string16::npos) {
761 label_->SetText(text);
762 label_line_2_->SetVisible(false);
763 } else {
764 label_->SetText(text.substr(0, position));
765 label_line_2_->SetText(text.substr(position + line_return.length()));
766 label_line_2_->SetVisible(true);
767 }
768}
769
770void AutofillDialogViews::SuggestionView::SetSuggestionIcon(
771 const gfx::Image& image) {
772 icon_->SetVisible(!image.IsEmpty());
773 icon_->SetImage(image.AsImageSkia());
774}
775
776void AutofillDialogViews::SuggestionView::ShowTextfield(
777 const string16& placeholder_text,
778 const gfx::ImageSkia& icon) {
779 decorated_->textfield()->set_placeholder_text(placeholder_text);
780 decorated_->textfield()->SetIcon(icon);
781 decorated_->SetVisible(true);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100782 // The textfield will increase the height of the first row and cause the
783 // label to be aligned properly, so the border is not necessary.
784 label_->set_border(NULL);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000785}
786
787// AutofilDialogViews::AutocheckoutProgressBar ---------------------------------
788
789AutofillDialogViews::AutocheckoutProgressBar::AutocheckoutProgressBar() {}
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +0100790AutofillDialogViews::AutocheckoutProgressBar::~AutocheckoutProgressBar() {}
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000791
792gfx::Size AutofillDialogViews::AutocheckoutProgressBar::GetPreferredSize() {
793 return gfx::Size(kAutocheckoutProgressBarWidth,
794 kAutocheckoutProgressBarHeight);
795}
796
797// AutofillDialogView ----------------------------------------------------------
798
799// static
800AutofillDialogView* AutofillDialogView::Create(
801 AutofillDialogController* controller) {
802 return new AutofillDialogViews(controller);
803}
804
805// AutofillDialogViews ---------------------------------------------------------
806
807AutofillDialogViews::AutofillDialogViews(AutofillDialogController* controller)
808 : controller_(controller),
809 window_(NULL),
810 contents_(NULL),
811 notification_area_(NULL),
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000812 account_chooser_(NULL),
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000813 sign_in_webview_(NULL),
814 main_container_(NULL),
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100815 scrollable_area_(NULL),
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000816 details_container_(NULL),
817 button_strip_extra_view_(NULL),
818 save_in_chrome_checkbox_(NULL),
819 autocheckout_progress_bar_view_(NULL),
820 autocheckout_progress_bar_(NULL),
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100821 footnote_view_(NULL),
822 legal_document_view_(NULL),
823 focus_manager_(NULL),
824 observer_(this) {
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000825 DCHECK(controller);
826 detail_groups_.insert(std::make_pair(SECTION_EMAIL,
827 DetailsGroup(SECTION_EMAIL)));
828 detail_groups_.insert(std::make_pair(SECTION_CC,
829 DetailsGroup(SECTION_CC)));
830 detail_groups_.insert(std::make_pair(SECTION_BILLING,
831 DetailsGroup(SECTION_BILLING)));
832 detail_groups_.insert(std::make_pair(SECTION_CC_BILLING,
833 DetailsGroup(SECTION_CC_BILLING)));
834 detail_groups_.insert(std::make_pair(SECTION_SHIPPING,
835 DetailsGroup(SECTION_SHIPPING)));
836}
837
838AutofillDialogViews::~AutofillDialogViews() {
839 DCHECK(!window_);
840}
841
842void AutofillDialogViews::Show() {
843 InitChildViews();
844 UpdateAccountChooser();
845 UpdateNotificationArea();
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +0100846 UpdateSaveInChromeCheckbox();
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000847
848 // Ownership of |contents_| is handed off by this call. The widget will take
849 // care of deleting itself after calling DeleteDelegate().
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000850 WebContentsModalDialogManager* web_contents_modal_dialog_manager =
851 WebContentsModalDialogManager::FromWebContents(
852 controller_->web_contents());
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100853 window_ = CreateWebContentsModalDialogViews(
854 this,
855 controller_->web_contents()->GetView()->GetNativeView(),
856 web_contents_modal_dialog_manager->delegate()->
857 GetWebContentsModalDialogHost());
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000858 web_contents_modal_dialog_manager->ShowDialog(window_->GetNativeView());
859 focus_manager_ = window_->GetFocusManager();
860 focus_manager_->AddFocusChangeListener(this);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100861
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100862#if defined(OS_WIN) && !defined(USE_AURA)
863 // On non-Aura Windows a standard accelerator gets registered that will
864 // navigate on a backspace. Override that here.
865 // TODO(abodenha): Remove this when no longer needed. See
866 // http://crbug.com/242584.
867 ui::Accelerator backspace(ui::VKEY_BACK, ui::EF_NONE);
868 focus_manager_->RegisterAccelerator(
869 backspace, ui::AcceleratorManager::kNormalPriority, this);
870#endif
871
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100872 // Listen for size changes on the browser.
873 views::Widget* browser_widget =
874 views::Widget::GetTopLevelWidgetForNativeView(
875 controller_->web_contents()->GetView()->GetNativeView());
876 observer_.Add(browser_widget);
877 OnWidgetBoundsChanged(browser_widget, gfx::Rect());
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000878}
879
880void AutofillDialogViews::Hide() {
881 if (window_)
882 window_->Close();
883}
884
885void AutofillDialogViews::UpdateAccountChooser() {
886 account_chooser_->Update();
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100887
888 // Update legal documents for the account.
889 if (footnote_view_) {
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +0100890 const string16 text = controller_->LegalDocumentsText();
891 legal_document_view_->SetText(text);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100892
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +0100893 if (!text.empty()) {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100894 const std::vector<ui::Range>& link_ranges =
895 controller_->LegalDocumentLinks();
896 for (size_t i = 0; i < link_ranges.size(); ++i) {
897 legal_document_view_->AddStyleRange(
898 link_ranges[i],
899 views::StyledLabel::RangeStyleInfo::CreateForLink());
900 }
901 }
902
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +0100903 footnote_view_->SetVisible(!text.empty());
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100904 ContentsPreferredSizeChanged();
905 }
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000906}
907
908void AutofillDialogViews::UpdateButtonStrip() {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100909 button_strip_extra_view_->SetVisible(
910 GetDialogButtons() != ui::DIALOG_BUTTON_NONE);
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +0100911 UpdateSaveInChromeCheckbox();
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000912 autocheckout_progress_bar_view_->SetVisible(
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +0100913 controller_->ShouldShowProgressBar());
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000914 GetDialogClientView()->UpdateDialogButtons();
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100915 ContentsPreferredSizeChanged();
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000916}
917
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +0100918void AutofillDialogViews::UpdateDetailArea() {
919 details_container_->SetVisible(controller_->ShouldShowDetailArea());
920 ContentsPreferredSizeChanged();
921}
922
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000923void AutofillDialogViews::UpdateNotificationArea() {
924 DCHECK(notification_area_);
925 notification_area_->SetNotifications(controller_->CurrentNotifications());
926 ContentsPreferredSizeChanged();
927}
928
929void AutofillDialogViews::UpdateSection(DialogSection section) {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100930 UpdateSectionImpl(section, true);
931}
932
933void AutofillDialogViews::FillSection(DialogSection section,
934 const DetailInput& originating_input) {
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000935 DetailsGroup* group = GroupForSection(section);
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +0100936 // Make sure to overwrite the originating input.
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100937 TextfieldMap::iterator text_mapping =
938 group->textfields.find(&originating_input);
939 if (text_mapping != group->textfields.end())
940 text_mapping->second->textfield()->SetText(string16());
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000941
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +0100942 // If the Autofill data comes from a credit card, make sure to overwrite the
943 // CC comboboxes (even if they already have something in them). If the
944 // Autofill data comes from an AutofillProfile, leave the comboboxes alone.
945 if ((section == SECTION_CC || section == SECTION_CC_BILLING) &&
946 AutofillType(originating_input.type).group() ==
947 AutofillType::CREDIT_CARD) {
948 for (ComboboxMap::const_iterator it = group->comboboxes.begin();
949 it != group->comboboxes.end(); ++it) {
950 if (AutofillType(it->first->type).group() == AutofillType::CREDIT_CARD)
951 it->second->SetSelectedIndex(it->second->model()->GetDefaultIndex());
952 }
953 }
954
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100955 UpdateSectionImpl(section, false);
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000956}
957
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000958void AutofillDialogViews::GetUserInput(DialogSection section,
959 DetailOutputMap* output) {
960 DetailsGroup* group = GroupForSection(section);
961 for (TextfieldMap::const_iterator it = group->textfields.begin();
962 it != group->textfields.end(); ++it) {
963 output->insert(std::make_pair(it->first, it->second->textfield()->text()));
964 }
965 for (ComboboxMap::const_iterator it = group->comboboxes.begin();
966 it != group->comboboxes.end(); ++it) {
967 output->insert(std::make_pair(it->first,
968 it->second->model()->GetItemAt(it->second->selected_index())));
969 }
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000970}
971
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000972string16 AutofillDialogViews::GetCvc() {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100973 DialogSection billing_section = controller_->SectionIsActive(SECTION_CC) ?
974 SECTION_CC : SECTION_CC_BILLING;
975 return GroupForSection(billing_section)->suggested_info->
976 decorated_textfield()->textfield()->text();
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000977}
978
979bool AutofillDialogViews::SaveDetailsLocally() {
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +0100980 DCHECK(save_in_chrome_checkbox_->visible());
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000981 return save_in_chrome_checkbox_->checked();
982}
983
984const content::NavigationController* AutofillDialogViews::ShowSignIn() {
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000985 // TODO(abodenha) We should be able to use the WebContents of the WebView
986 // to navigate instead of LoadInitialURL. Figure out why it doesn't work.
987
988 sign_in_webview_->LoadInitialURL(wallet::GetSignInUrl());
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100989
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000990 main_container_->SetVisible(false);
Torne (Richard Coles)a93a17c2013-05-15 11:34:50 +0100991 sign_in_webview_->SetVisible(true);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100992 UpdateButtonStrip();
993 ContentsPreferredSizeChanged();
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000994 return &sign_in_webview_->web_contents()->GetController();
995}
996
997void AutofillDialogViews::HideSignIn() {
Torne (Richard Coles)a93a17c2013-05-15 11:34:50 +0100998 sign_in_webview_->SetVisible(false);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000999 main_container_->SetVisible(true);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001000 UpdateButtonStrip();
1001 ContentsPreferredSizeChanged();
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001002}
1003
1004void AutofillDialogViews::UpdateProgressBar(double value) {
1005 autocheckout_progress_bar_->SetValue(value);
1006}
1007
1008void AutofillDialogViews::ModelChanged() {
1009 menu_runner_.reset();
1010
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001011 for (DetailGroupMap::const_iterator iter = detail_groups_.begin();
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001012 iter != detail_groups_.end(); ++iter) {
1013 UpdateDetailsGroupState(iter->second);
1014 }
1015}
1016
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001017TestableAutofillDialogView* AutofillDialogViews::GetTestableView() {
1018 return this;
1019}
1020
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +01001021void AutofillDialogViews::OnSignInResize(const gfx::Size& pref_size) {
1022 sign_in_webview_->SetPreferredSize(pref_size);
1023 ContentsPreferredSizeChanged();
1024}
1025
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001026void AutofillDialogViews::SubmitForTesting() {
1027 Accept();
1028}
1029
1030void AutofillDialogViews::CancelForTesting() {
1031 if (Cancel())
1032 Hide();
1033}
1034
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001035string16 AutofillDialogViews::GetTextContentsOfInput(const DetailInput& input) {
1036 views::Textfield* textfield = TextfieldForInput(input);
1037 if (textfield)
1038 return textfield->text();
1039
1040 views::Combobox* combobox = ComboboxForInput(input);
1041 if (combobox)
1042 return combobox->model()->GetItemAt(combobox->selected_index());
1043
1044 NOTREACHED();
1045 return string16();
1046}
1047
1048void AutofillDialogViews::SetTextContentsOfInput(const DetailInput& input,
1049 const string16& contents) {
1050 views::Textfield* textfield = TextfieldForInput(input);
1051 if (textfield) {
1052 TextfieldForInput(input)->SetText(contents);
1053 return;
1054 }
1055
1056 views::Combobox* combobox = ComboboxForInput(input);
1057 if (combobox) {
1058 for (int i = 0; i < combobox->model()->GetItemCount(); ++i) {
1059 if (contents == combobox->model()->GetItemAt(i)) {
1060 combobox->SetSelectedIndex(i);
1061 return;
1062 }
1063 }
1064 // If we don't find a match, return the combobox to its default state.
1065 combobox->SetSelectedIndex(combobox->model()->GetDefaultIndex());
1066 return;
1067 }
1068
1069 NOTREACHED();
1070}
1071
1072void AutofillDialogViews::ActivateInput(const DetailInput& input) {
1073 TextfieldEditedOrActivated(TextfieldForInput(input), false);
1074}
1075
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +01001076gfx::Size AutofillDialogViews::GetSize() const {
1077 return GetWidget() ? GetWidget()->GetRootView()->size() : gfx::Size();
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +01001078}
1079
1080bool AutofillDialogViews::AcceleratorPressed(
1081 const ui::Accelerator& accelerator) {
1082 ui::KeyboardCode key = accelerator.key_code();
1083 if (key == ui::VKEY_BACK)
1084 return true;
1085 return false;
1086}
1087
1088bool AutofillDialogViews::CanHandleAccelerators() const {
1089 return true;
1090}
1091
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001092string16 AutofillDialogViews::GetWindowTitle() const {
1093 return controller_->DialogTitle();
1094}
1095
1096void AutofillDialogViews::WindowClosing() {
1097 focus_manager_->RemoveFocusChangeListener(this);
1098}
1099
1100void AutofillDialogViews::DeleteDelegate() {
1101 window_ = NULL;
1102 // |this| belongs to |controller_|.
1103 controller_->ViewClosed();
1104}
1105
1106views::Widget* AutofillDialogViews::GetWidget() {
1107 return contents_->GetWidget();
1108}
1109
1110const views::Widget* AutofillDialogViews::GetWidget() const {
1111 return contents_->GetWidget();
1112}
1113
1114views::View* AutofillDialogViews::GetContentsView() {
1115 return contents_;
1116}
1117
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001118int AutofillDialogViews::GetDialogButtons() const {
Torne (Richard Coles)a93a17c2013-05-15 11:34:50 +01001119 if (sign_in_webview_->visible())
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001120 return ui::DIALOG_BUTTON_NONE;
1121
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +01001122 return controller_->GetDialogButtons();
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001123}
1124
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001125string16 AutofillDialogViews::GetDialogButtonLabel(ui::DialogButton button)
1126 const {
1127 return button == ui::DIALOG_BUTTON_OK ?
1128 controller_->ConfirmButtonText() : controller_->CancelButtonText();
1129}
1130
1131bool AutofillDialogViews::IsDialogButtonEnabled(ui::DialogButton button) const {
1132 return controller_->IsDialogButtonEnabled(button);
1133}
1134
1135views::View* AutofillDialogViews::CreateExtraView() {
1136 return button_strip_extra_view_;
1137}
1138
1139views::View* AutofillDialogViews::CreateTitlebarExtraView() {
1140 return account_chooser_;
1141}
1142
1143views::View* AutofillDialogViews::CreateFootnoteView() {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001144 footnote_view_ = new views::View();
1145 footnote_view_->SetLayoutManager(
1146 new views::BoxLayout(views::BoxLayout::kVertical,
1147 kLegalDocPadding,
1148 kLegalDocPadding,
1149 0));
1150 footnote_view_->set_border(
1151 views::Border::CreateSolidSidedBorder(1, 0, 0, 0, kSubtleBorderColor));
1152 footnote_view_->set_background(
1153 views::Background::CreateSolidBackground(kShadingColor));
1154
1155 legal_document_view_ = new views::StyledLabel(string16(), this);
1156 footnote_view_->AddChildView(legal_document_view_);
1157 footnote_view_->SetVisible(false);
1158
1159 return footnote_view_;
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001160}
1161
1162bool AutofillDialogViews::Cancel() {
1163 controller_->OnCancel();
1164 return true;
1165}
1166
1167bool AutofillDialogViews::Accept() {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001168 if (ValidateForm())
1169 controller_->OnAccept();
Torne (Richard Coles)a93a17c2013-05-15 11:34:50 +01001170 else if (!validity_map_.empty())
1171 validity_map_.begin()->first->RequestFocus();
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001172
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001173 // |controller_| decides when to hide the dialog.
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001174 return false;
1175}
1176
1177// TODO(wittman): Remove this override once we move to the new style frame view
1178// on all dialogs.
1179views::NonClientFrameView* AutofillDialogViews::CreateNonClientFrameView(
1180 views::Widget* widget) {
1181 return CreateConstrainedStyleNonClientFrameView(
1182 widget,
1183 controller_->web_contents()->GetBrowserContext());
1184}
1185
1186void AutofillDialogViews::ButtonPressed(views::Button* sender,
1187 const ui::Event& event) {
Torne (Richard Coles)a93a17c2013-05-15 11:34:50 +01001188 // TODO(estade): Should the menu be shown on mouse down?
1189 DetailsGroup* group = NULL;
1190 for (DetailGroupMap::iterator iter = detail_groups_.begin();
1191 iter != detail_groups_.end(); ++iter) {
1192 if (sender == iter->second.suggested_button) {
1193 group = &iter->second;
1194 break;
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001195 }
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001196 }
Torne (Richard Coles)a93a17c2013-05-15 11:34:50 +01001197 DCHECK(group);
1198
1199 if (!group->suggested_button->visible())
1200 return;
1201
1202 menu_runner_.reset(new views::MenuRunner(
1203 controller_->MenuModelForSection(group->section)));
1204
1205 group->container->SetActive(true);
1206 views::Button::ButtonState state = group->suggested_button->state();
1207 group->suggested_button->SetState(views::Button::STATE_PRESSED);
1208 // Ignore the result since we don't need to handle a deleted menu specially.
1209 gfx::Rect bounds = group->suggested_button->GetBoundsInScreen();
1210 bounds.Inset(group->suggested_button->GetInsets());
1211 ignore_result(
1212 menu_runner_->RunMenuAt(sender->GetWidget(),
1213 NULL,
1214 bounds,
1215 views::MenuItemView::TOPRIGHT,
1216 0));
1217 group->container->SetActive(false);
1218 group->suggested_button->SetState(state);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001219}
1220
1221void AutofillDialogViews::ContentsChanged(views::Textfield* sender,
1222 const string16& new_contents) {
1223 TextfieldEditedOrActivated(sender, true);
1224}
1225
1226bool AutofillDialogViews::HandleKeyEvent(views::Textfield* sender,
1227 const ui::KeyEvent& key_event) {
1228 scoped_ptr<ui::KeyEvent> copy(key_event.Copy());
1229#if defined(OS_WIN) && !defined(USE_AURA)
1230 content::NativeWebKeyboardEvent event(copy->native_event());
1231#else
1232 content::NativeWebKeyboardEvent event(copy.get());
1233#endif
1234 return controller_->HandleKeyPressEventInInput(event);
1235}
1236
1237bool AutofillDialogViews::HandleMouseEvent(views::Textfield* sender,
1238 const ui::MouseEvent& mouse_event) {
Torne (Richard Coles)a93a17c2013-05-15 11:34:50 +01001239 if (mouse_event.IsLeftMouseButton() && sender->HasFocus()) {
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001240 TextfieldEditedOrActivated(sender, false);
Torne (Richard Coles)a93a17c2013-05-15 11:34:50 +01001241 // Show an error bubble if a user clicks on an input that's already focused
1242 // (and invalid).
1243 ShowErrorBubbleForViewIfNecessary(sender);
1244 }
1245
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001246 return false;
1247}
1248
1249void AutofillDialogViews::OnWillChangeFocus(
1250 views::View* focused_before,
1251 views::View* focused_now) {
1252 controller_->FocusMoved();
Torne (Richard Coles)a93a17c2013-05-15 11:34:50 +01001253 error_bubble_.reset();
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001254}
1255
1256void AutofillDialogViews::OnDidChangeFocus(
1257 views::View* focused_before,
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001258 views::View* focused_now) {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001259 // If user leaves an edit-field, revalidate the group it belongs to.
Torne (Richard Coles)a93a17c2013-05-15 11:34:50 +01001260 if (focused_before) {
1261 DetailsGroup* group = GroupForView(focused_before);
1262 if (group && group->container->visible())
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +01001263 ValidateGroup(*group, VALIDATE_EDIT);
Torne (Richard Coles)a93a17c2013-05-15 11:34:50 +01001264 }
1265
1266 // Show an error bubble when the user focuses the input.
1267 if (focused_now)
1268 ShowErrorBubbleForViewIfNecessary(focused_now);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001269}
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001270
1271void AutofillDialogViews::LinkClicked(views::Link* source, int event_flags) {
1272 // Edit links.
1273 for (DetailGroupMap::iterator iter = detail_groups_.begin();
1274 iter != detail_groups_.end(); ++iter) {
1275 if (iter->second.suggested_info->Contains(source)) {
1276 controller_->EditClickedForSection(iter->first);
1277 return;
1278 }
1279 }
1280}
1281
1282void AutofillDialogViews::OnSelectedIndexChanged(views::Combobox* combobox) {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001283 DetailsGroup* group = GroupForView(combobox);
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +01001284 ValidateGroup(*group, VALIDATE_EDIT);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001285}
1286
1287void AutofillDialogViews::StyledLabelLinkClicked(const ui::Range& range,
1288 int event_flags) {
1289 controller_->LegalDocumentLinkClicked(range);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001290}
1291
1292void AutofillDialogViews::InitChildViews() {
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +01001293 button_strip_extra_view_ = new ButtonStripView();
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001294 button_strip_extra_view_->SetLayoutManager(
1295 new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, 0));
1296
1297 save_in_chrome_checkbox_ =
1298 new views::Checkbox(controller_->SaveLocallyText());
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +01001299 save_in_chrome_checkbox_->SetChecked(true);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001300 button_strip_extra_view_->AddChildView(save_in_chrome_checkbox_);
1301
1302 autocheckout_progress_bar_view_ = new views::View();
1303 autocheckout_progress_bar_view_->SetLayoutManager(
1304 new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0));
1305
1306 views::Label* progress_bar_label = new views::Label();
1307 progress_bar_label->SetText(controller_->ProgressBarText());
1308 progress_bar_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
1309 autocheckout_progress_bar_view_->AddChildView(progress_bar_label);
1310
1311 autocheckout_progress_bar_ = new AutocheckoutProgressBar();
1312 autocheckout_progress_bar_view_->AddChildView(autocheckout_progress_bar_);
1313
1314 button_strip_extra_view_->AddChildView(autocheckout_progress_bar_view_);
1315 autocheckout_progress_bar_view_->SetVisible(false);
1316
1317 contents_ = new views::View();
1318 contents_->SetLayoutManager(
1319 new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, 0));
1320 contents_->AddChildView(CreateMainContainer());
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001321 sign_in_webview_ = new views::WebView(controller_->profile());
Torne (Richard Coles)a93a17c2013-05-15 11:34:50 +01001322 sign_in_webview_->SetVisible(false);
1323 contents_->AddChildView(sign_in_webview_);
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +01001324 sign_in_delegate_.reset(
1325 new AutofillDialogSignInDelegate(this,
1326 sign_in_webview_->GetWebContents()));
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001327}
1328
1329views::View* AutofillDialogViews::CreateMainContainer() {
1330 main_container_ = new views::View();
1331 main_container_->SetLayoutManager(
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001332 new views::BoxLayout(views::BoxLayout::kVertical, 0, 0,
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001333 views::kRelatedControlVerticalSpacing));
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001334
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001335 account_chooser_ = new AccountChooser(controller_);
1336 if (!views::DialogDelegate::UseNewStyle())
1337 main_container_->AddChildView(account_chooser_);
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001338
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001339 notification_area_ = new NotificationArea(controller_);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001340 notification_area_->set_arrow_centering_anchor(account_chooser_->AsWeakPtr());
1341 main_container_->AddChildView(notification_area_);
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001342
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001343 scrollable_area_ = new SizeLimitedScrollView(CreateDetailsContainer());
1344 main_container_->AddChildView(scrollable_area_);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001345 return main_container_;
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001346}
1347
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001348views::View* AutofillDialogViews::CreateDetailsContainer() {
Torne (Richard Coles)a93a17c2013-05-15 11:34:50 +01001349 details_container_ = new DetailsContainerView(
1350 base::Bind(&AutofillDialogViews::DetailsContainerBoundsChanged,
1351 base::Unretained(this)));
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001352 // A box layout is used because it respects widget visibility.
1353 details_container_->SetLayoutManager(
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001354 new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0));
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001355 for (DetailGroupMap::iterator iter = detail_groups_.begin();
1356 iter != detail_groups_.end(); ++iter) {
1357 CreateDetailsSection(iter->second.section);
1358 details_container_->AddChildView(iter->second.container);
1359 }
1360
1361 return details_container_;
1362}
1363
1364void AutofillDialogViews::CreateDetailsSection(DialogSection section) {
1365 // Inputs container (manual inputs + combobox).
1366 views::View* inputs_container = CreateInputsContainer(section);
1367
1368 DetailsGroup* group = GroupForSection(section);
1369 // Container (holds label + inputs).
1370 group->container = new SectionContainer(
1371 controller_->LabelForSection(section),
1372 inputs_container,
1373 group->suggested_button);
1374 UpdateDetailsGroupState(*group);
1375}
1376
1377views::View* AutofillDialogViews::CreateInputsContainer(DialogSection section) {
1378 views::View* inputs_container = new views::View();
1379 views::GridLayout* layout = new views::GridLayout(inputs_container);
1380 inputs_container->SetLayoutManager(layout);
1381
1382 int kColumnSetId = 0;
1383 views::ColumnSet* column_set = layout->AddColumnSet(kColumnSetId);
1384 column_set->AddColumn(views::GridLayout::FILL,
1385 views::GridLayout::LEADING,
1386 1,
1387 views::GridLayout::USE_PREF,
1388 0,
1389 0);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001390 // A column for the menu button.
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001391 column_set->AddColumn(views::GridLayout::CENTER,
1392 views::GridLayout::LEADING,
1393 0,
1394 views::GridLayout::USE_PREF,
1395 0,
1396 0);
1397 layout->StartRow(0, kColumnSetId);
1398
1399 // The |info_view| holds |manual_inputs| and |suggested_info|, allowing the
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001400 // dialog to toggle which is shown.
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001401 views::View* info_view = new views::View();
1402 info_view->SetLayoutManager(
1403 new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0));
1404
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001405 views::View* manual_inputs = InitInputsView(section);
1406 info_view->AddChildView(manual_inputs);
1407 SuggestionView* suggested_info =
1408 new SuggestionView(controller_->EditSuggestionText(), this);
1409 info_view->AddChildView(suggested_info);
1410 layout->AddView(info_view);
1411
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001412 views::ImageButton* menu_button = new views::ImageButton(this);
1413 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001414 menu_button->SetImage(views::Button::STATE_NORMAL,
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001415 rb.GetImageSkiaNamed(IDR_AUTOFILL_DIALOG_MENU_BUTTON));
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001416 menu_button->SetImage(views::Button::STATE_PRESSED,
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001417 rb.GetImageSkiaNamed(IDR_AUTOFILL_DIALOG_MENU_BUTTON_P));
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001418 menu_button->SetImage(views::Button::STATE_HOVERED,
1419 rb.GetImageSkiaNamed(IDR_AUTOFILL_DIALOG_MENU_BUTTON_H));
1420 menu_button->SetImage(views::Button::STATE_DISABLED,
1421 rb.GetImageSkiaNamed(IDR_AUTOFILL_DIALOG_MENU_BUTTON_D));
1422 menu_button->set_border(views::Border::CreateEmptyBorder(
1423 kMenuButtonTopOffset,
1424 kMenuButtonHorizontalPadding,
1425 0,
1426 kMenuButtonHorizontalPadding));
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001427 layout->AddView(menu_button);
1428
1429 DetailsGroup* group = GroupForSection(section);
1430 group->suggested_button = menu_button;
1431 group->manual_input = manual_inputs;
1432 group->suggested_info = suggested_info;
1433 return inputs_container;
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001434}
1435
1436// TODO(estade): we should be using Chrome-style constrained window padding
1437// values.
1438views::View* AutofillDialogViews::InitInputsView(DialogSection section) {
1439 const DetailInputs& inputs = controller_->RequestedFieldsForSection(section);
1440 TextfieldMap* textfields = &GroupForSection(section)->textfields;
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001441 ComboboxMap* comboboxes = &GroupForSection(section)->comboboxes;
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001442
1443 views::View* view = new views::View();
1444 views::GridLayout* layout = new views::GridLayout(view);
1445 view->SetLayoutManager(layout);
1446
1447 for (DetailInputs::const_iterator it = inputs.begin();
1448 it != inputs.end(); ++it) {
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001449 const DetailInput& input = *it;
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001450 int kColumnSetId = input.row_id;
1451 views::ColumnSet* column_set = layout->GetColumnSet(kColumnSetId);
1452 if (!column_set) {
1453 // Create a new column set and row.
1454 column_set = layout->AddColumnSet(kColumnSetId);
1455 if (it != inputs.begin())
1456 layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
1457 layout->StartRow(0, kColumnSetId);
1458 } else {
1459 // Add a new column to existing row.
1460 column_set->AddPaddingColumn(0, views::kRelatedControlHorizontalSpacing);
1461 // Must explicitly skip the padding column since we've already started
1462 // adding views.
1463 layout->SkipColumns(1);
1464 }
1465
1466 float expand = input.expand_weight;
1467 column_set->AddColumn(views::GridLayout::FILL,
1468 views::GridLayout::BASELINE,
1469 expand ? expand : 1,
1470 views::GridLayout::USE_PREF,
1471 0,
1472 0);
1473
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001474 ui::ComboboxModel* input_model =
1475 controller_->ComboboxModelForAutofillType(input.type);
1476 if (input_model) {
1477 views::Combobox* combobox = new views::Combobox(input_model);
1478 combobox->set_listener(this);
1479 comboboxes->insert(std::make_pair(&input, combobox));
1480 layout->AddView(combobox);
1481
1482 for (int i = 0; i < input_model->GetItemCount(); ++i) {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001483 if (input.initial_value == input_model->GetItemAt(i)) {
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001484 combobox->SetSelectedIndex(i);
1485 break;
1486 }
1487 }
1488 } else {
1489 DecoratedTextfield* field = new DecoratedTextfield(
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001490 input.initial_value,
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001491 l10n_util::GetStringUTF16(input.placeholder_text_rid),
1492 this);
1493
1494 gfx::Image icon =
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001495 controller_->IconForField(input.type, input.initial_value);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001496 field->textfield()->SetIcon(icon.AsImageSkia());
1497
1498 textfields->insert(std::make_pair(&input, field));
1499 layout->AddView(field);
1500 }
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001501 }
1502
1503 return view;
1504}
1505
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001506void AutofillDialogViews::UpdateSectionImpl(
1507 DialogSection section,
1508 bool clobber_inputs) {
1509 const DetailInputs& updated_inputs =
1510 controller_->RequestedFieldsForSection(section);
1511 DetailsGroup* group = GroupForSection(section);
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001512
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001513 for (DetailInputs::const_iterator iter = updated_inputs.begin();
1514 iter != updated_inputs.end(); ++iter) {
1515 const DetailInput& input = *iter;
1516 TextfieldMap::iterator text_mapping = group->textfields.find(&input);
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001517
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001518 if (text_mapping != group->textfields.end()) {
1519 views::Textfield* textfield = text_mapping->second->textfield();
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +01001520 textfield->SetEnabled(input.editable);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001521 if (textfield->text().empty() || clobber_inputs) {
1522 textfield->SetText(iter->initial_value);
1523 textfield->SetIcon(controller_->IconForField(
1524 input.type, textfield->text()).AsImageSkia());
1525 }
1526 }
1527
1528 ComboboxMap::iterator combo_mapping = group->comboboxes.find(&input);
1529 if (combo_mapping != group->comboboxes.end()) {
1530 views::Combobox* combobox = combo_mapping->second;
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +01001531 combobox->SetEnabled(input.editable);
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +01001532 if (combobox->selected_index() == combobox->model()->GetDefaultIndex() ||
1533 clobber_inputs) {
1534 for (int i = 0; i < combobox->model()->GetItemCount(); ++i) {
1535 if (input.initial_value == combobox->model()->GetItemAt(i)) {
1536 combobox->SetSelectedIndex(i);
1537 break;
1538 }
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001539 }
1540 }
1541 }
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001542 }
1543
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001544 UpdateDetailsGroupState(*group);
1545}
1546
1547void AutofillDialogViews::UpdateDetailsGroupState(const DetailsGroup& group) {
1548 const SuggestionState& suggestion_state =
1549 controller_->SuggestionStateForSection(group.section);
1550 bool show_suggestions = !suggestion_state.text.empty();
1551 group.suggested_info->SetVisible(show_suggestions);
1552 group.suggested_info->SetSuggestionText(suggestion_state.text,
1553 suggestion_state.text_style);
1554 group.suggested_info->SetSuggestionIcon(suggestion_state.icon);
1555 group.suggested_info->SetEditable(suggestion_state.editable);
1556
1557 if (!suggestion_state.extra_text.empty()) {
1558 group.suggested_info->ShowTextfield(
1559 suggestion_state.extra_text,
1560 suggestion_state.extra_icon.AsImageSkia());
1561 }
1562
1563 group.manual_input->SetVisible(!show_suggestions);
1564
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001565 // Show or hide the "Save in chrome" checkbox. If nothing is in editing mode,
1566 // hide. If the controller tells us not to show it, likewise hide.
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +01001567 UpdateSaveInChromeCheckbox();
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001568
1569 const bool has_menu = !!controller_->MenuModelForSection(group.section);
1570
1571 if (group.suggested_button)
1572 group.suggested_button->SetVisible(has_menu);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001573
1574 if (group.container) {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001575 group.container->SetForwardMouseEvents(has_menu && show_suggestions);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001576 group.container->SetVisible(controller_->SectionIsActive(group.section));
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001577 if (group.container->visible())
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +01001578 ValidateGroup(group, VALIDATE_EDIT);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001579 }
1580
1581 ContentsPreferredSizeChanged();
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001582}
1583
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001584template<class T>
1585void AutofillDialogViews::SetValidityForInput(
1586 T* input,
1587 const string16& message) {
1588 bool invalid = !message.empty();
1589 input->SetInvalid(invalid);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001590
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001591 if (invalid) {
1592 validity_map_[input] = message;
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001593 } else {
1594 validity_map_.erase(input);
Torne (Richard Coles)a93a17c2013-05-15 11:34:50 +01001595
1596 if (error_bubble_ && error_bubble_->anchor() == input) {
1597 validity_map_.erase(input);
1598 error_bubble_.reset();
1599 }
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001600 }
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001601}
1602
Torne (Richard Coles)a93a17c2013-05-15 11:34:50 +01001603void AutofillDialogViews::ShowErrorBubbleForViewIfNecessary(views::View* view) {
1604 if (!view->GetWidget())
1605 return;
1606
1607 views::View* input =
1608 view->GetAncestorWithClassName(kDecoratedTextfieldClassName);
1609 if (!input)
1610 input = view;
1611
1612 if (error_bubble_ && error_bubble_->anchor() == input)
1613 return;
1614
1615 std::map<views::View*, string16>::iterator error_message =
1616 validity_map_.find(input);
1617 if (error_message != validity_map_.end())
1618 error_bubble_.reset(new ErrorBubble(input, error_message->second));
1619}
1620
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +01001621bool AutofillDialogViews::ValidateGroup(const DetailsGroup& group,
1622 ValidationType validation_type) {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001623 DCHECK(group.container->visible());
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001624
1625 scoped_ptr<DetailInput> cvc_input;
1626 DetailOutputMap detail_outputs;
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001627 typedef std::map<AutofillFieldType, base::Callback<void(const string16&)> >
1628 FieldMap;
1629 FieldMap field_map;
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001630
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001631 if (group.manual_input->visible()) {
1632 for (TextfieldMap::const_iterator iter = group.textfields.begin();
1633 iter != group.textfields.end(); ++iter) {
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +01001634 if (!iter->first->editable)
1635 continue;
1636
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001637 detail_outputs[iter->first] = iter->second->textfield()->text();
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001638 field_map[iter->first->type] = base::Bind(
1639 &AutofillDialogViews::SetValidityForInput<DecoratedTextfield>,
1640 base::Unretained(this),
1641 iter->second);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001642 }
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001643 for (ComboboxMap::const_iterator iter = group.comboboxes.begin();
1644 iter != group.comboboxes.end(); ++iter) {
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +01001645 if (!iter->first->editable)
1646 continue;
1647
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001648 views::Combobox* combobox = iter->second;
1649 string16 item =
1650 combobox->model()->GetItemAt(combobox->selected_index());
1651 detail_outputs[iter->first] = item;
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001652 field_map[iter->first->type] = base::Bind(
1653 &AutofillDialogViews::SetValidityForInput<views::Combobox>,
1654 base::Unretained(this),
1655 iter->second);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001656 }
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001657 } else if (group.section == SECTION_CC) {
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001658 DecoratedTextfield* decorated_cvc =
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001659 group.suggested_info->decorated_textfield();
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001660 cvc_input.reset(new DetailInput);
1661 cvc_input->type = CREDIT_CARD_VERIFICATION_CODE;
1662 detail_outputs[cvc_input.get()] = decorated_cvc->textfield()->text();
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001663 field_map[cvc_input->type] = base::Bind(
1664 &AutofillDialogViews::SetValidityForInput<DecoratedTextfield>,
1665 base::Unretained(this),
1666 decorated_cvc);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001667 }
1668
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001669 ValidityData invalid_inputs;
1670 invalid_inputs = controller_->InputsAreValid(detail_outputs, validation_type);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001671 // Flag invalid fields, removing them from |field_map|.
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001672 for (ValidityData::const_iterator iter =
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001673 invalid_inputs.begin(); iter != invalid_inputs.end(); ++iter) {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001674 const string16& message = iter->second;
1675 field_map[iter->first].Run(message);
1676 field_map.erase(iter->first);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001677 }
1678
1679 // The remaining fields in |field_map| are valid. Mark them as such.
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001680 for (FieldMap::iterator iter = field_map.begin(); iter != field_map.end();
1681 ++iter) {
1682 iter->second.Run(string16());
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001683 }
1684
1685 return invalid_inputs.empty();
1686}
1687
1688bool AutofillDialogViews::ValidateForm() {
1689 bool all_valid = true;
1690 for (DetailGroupMap::iterator iter = detail_groups_.begin();
1691 iter != detail_groups_.end(); ++iter) {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001692 const DetailsGroup& group = iter->second;
1693 if (!group.container->visible())
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001694 continue;
1695
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +01001696 if (!ValidateGroup(group, VALIDATE_FINAL))
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001697 all_valid = false;
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001698 }
1699
1700 return all_valid;
1701}
1702
1703void AutofillDialogViews::TextfieldEditedOrActivated(
1704 views::Textfield* textfield,
1705 bool was_edit) {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001706 DetailsGroup* group = GroupForView(textfield);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001707 DCHECK(group);
1708
1709 // Figure out the AutofillFieldType this textfield represents.
1710 AutofillFieldType type = UNKNOWN_TYPE;
1711 DecoratedTextfield* decorated = NULL;
1712
1713 // Look for the input in the manual inputs.
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001714 views::View* ancestor =
1715 textfield->GetAncestorWithClassName(kDecoratedTextfieldClassName);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001716 for (TextfieldMap::const_iterator iter = group->textfields.begin();
1717 iter != group->textfields.end();
1718 ++iter) {
1719 decorated = iter->second;
1720 if (decorated == ancestor) {
1721 controller_->UserEditedOrActivatedInput(iter->first,
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001722 GetWidget()->GetNativeView(),
1723 textfield->GetBoundsInScreen(),
1724 textfield->text(),
1725 was_edit);
1726 type = iter->first->type;
1727 break;
1728 }
1729 }
1730
1731 if (ancestor == group->suggested_info->decorated_textfield()) {
1732 decorated = group->suggested_info->decorated_textfield();
1733 type = CREDIT_CARD_VERIFICATION_CODE;
1734 }
1735 DCHECK_NE(UNKNOWN_TYPE, type);
1736
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001737 // If the field is marked as invalid, check if the text is now valid.
1738 // Many fields (i.e. CC#) are invalid for most of the duration of editing,
1739 // so flagging them as invalid prematurely is not helpful. However,
1740 // correcting a minor mistake (i.e. a wrong CC digit) should immediately
1741 // result in validation - positive user feedback.
1742 if (decorated->invalid() && was_edit) {
Torne (Richard Coles)a93a17c2013-05-15 11:34:50 +01001743 SetValidityForInput<DecoratedTextfield>(
1744 decorated,
1745 controller_->InputValidityMessage(type, textfield->text()));
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001746
1747 // If the field transitioned from invalid to valid, re-validate the group,
1748 // since inter-field checks become meaningful with valid fields.
1749 if (!decorated->invalid())
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +01001750 ValidateGroup(*group, VALIDATE_EDIT);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001751 }
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001752
1753 gfx::Image icon = controller_->IconForField(type, textfield->text());
1754 textfield->SetIcon(icon.AsImageSkia());
1755}
1756
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +01001757void AutofillDialogViews::UpdateSaveInChromeCheckbox() {
1758 save_in_chrome_checkbox_->SetVisible(
1759 controller_->ShouldOfferToSaveInChrome());
1760}
1761
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001762void AutofillDialogViews::ContentsPreferredSizeChanged() {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001763 if (GetWidget()) {
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001764 GetWidget()->SetSize(GetWidget()->non_client_view()->GetPreferredSize());
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001765 // If the above line does not cause the dialog's size to change, |contents_|
1766 // may not be laid out. This will trigger a layout only if it's needed.
1767 contents_->SetBoundsRect(contents_->bounds());
Torne (Richard Coles)a93a17c2013-05-15 11:34:50 +01001768
1769 if (error_bubble_)
1770 error_bubble_->UpdatePosition();
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001771 }
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001772}
1773
1774AutofillDialogViews::DetailsGroup* AutofillDialogViews::GroupForSection(
1775 DialogSection section) {
1776 return &detail_groups_.find(section)->second;
1777}
1778
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001779AutofillDialogViews::DetailsGroup* AutofillDialogViews::GroupForView(
1780 views::View* view) {
1781 DCHECK(view);
1782 // Textfields are treated slightly differently. For them, inspect
1783 // the DecoratedTextfield ancestor, not the actual control.
1784 views::View* ancestor =
1785 view->GetAncestorWithClassName(kDecoratedTextfieldClassName);
1786
1787 views::View* control = ancestor ? ancestor : view;
1788 for (DetailGroupMap::iterator iter = detail_groups_.begin();
1789 iter != detail_groups_.end(); ++iter) {
1790 DetailsGroup* group = &iter->second;
1791 if (control->parent() == group->manual_input)
1792 return group;
1793
1794 // Textfields need to check a second case, since they can be
1795 // suggested inputs instead of directly editable inputs. Those are
1796 // accessed via |suggested_info|.
1797 if (ancestor &&
1798 ancestor == group->suggested_info->decorated_textfield()) {
1799 return group;
1800 }
1801 }
1802 return NULL;
1803}
1804
1805views::Textfield* AutofillDialogViews::TextfieldForInput(
1806 const DetailInput& input) {
1807 for (DetailGroupMap::iterator iter = detail_groups_.begin();
1808 iter != detail_groups_.end(); ++iter) {
1809 const DetailsGroup& group = iter->second;
1810 TextfieldMap::const_iterator text_mapping = group.textfields.find(&input);
1811 if (text_mapping != group.textfields.end())
1812 return text_mapping->second->textfield();
1813 }
1814
1815 return NULL;
1816}
1817
1818views::Combobox* AutofillDialogViews::ComboboxForInput(
1819 const DetailInput& input) {
1820 for (DetailGroupMap::iterator iter = detail_groups_.begin();
1821 iter != detail_groups_.end(); ++iter) {
1822 const DetailsGroup& group = iter->second;
1823 ComboboxMap::const_iterator combo_mapping = group.comboboxes.find(&input);
1824 if (combo_mapping != group.comboboxes.end())
1825 return combo_mapping->second;
1826 }
1827
1828 return NULL;
1829}
1830
Torne (Richard Coles)a93a17c2013-05-15 11:34:50 +01001831void AutofillDialogViews::DetailsContainerBoundsChanged() {
1832 if (error_bubble_)
1833 error_bubble_->UpdatePosition();
1834}
1835
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001836AutofillDialogViews::DetailsGroup::DetailsGroup(DialogSection section)
1837 : section(section),
1838 container(NULL),
1839 manual_input(NULL),
1840 suggested_info(NULL),
1841 suggested_button(NULL) {}
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001842
1843AutofillDialogViews::DetailsGroup::~DetailsGroup() {}
1844
1845} // namespace autofill