Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(668)

Side by Side Diff: chrome/browser/ui/views/autofill/autofill_dialog_views.cc

Issue 1931043002: Remove requestAutocomplete (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: rebase Created 4 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // 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
7 #include <stddef.h>
8
9 #include <utility>
10
11 #include "base/bind.h"
12 #include "base/location.h"
13 #include "base/macros.h"
14 #include "base/strings/utf_string_conversions.h"
15 #include "chrome/browser/profiles/profile.h"
16 #include "chrome/browser/ui/autofill/autofill_dialog_view_delegate.h"
17 #include "chrome/browser/ui/autofill/loading_animation.h"
18 #include "chrome/browser/ui/views/autofill/expanding_textfield.h"
19 #include "chrome/browser/ui/views/autofill/info_bubble.h"
20 #include "chrome/browser/ui/views/autofill/tooltip_icon.h"
21 #include "components/autofill/core/browser/autofill_type.h"
22 #include "components/constrained_window/constrained_window_views.h"
23 #include "components/web_modal/web_contents_modal_dialog_host.h"
24 #include "components/web_modal/web_contents_modal_dialog_manager.h"
25 #include "components/web_modal/web_contents_modal_dialog_manager_delegate.h"
26 #include "content/public/browser/native_web_keyboard_event.h"
27 #include "content/public/browser/navigation_controller.h"
28 #include "content/public/browser/web_contents.h"
29 #include "grit/theme_resources.h"
30 #include "third_party/skia/include/core/SkColor.h"
31 #include "ui/base/models/combobox_model.h"
32 #include "ui/base/models/menu_model.h"
33 #include "ui/base/resource/resource_bundle.h"
34 #include "ui/compositor/paint_recorder.h"
35 #include "ui/events/event_handler.h"
36 #include "ui/gfx/animation/animation_delegate.h"
37 #include "ui/gfx/canvas.h"
38 #include "ui/gfx/color_utils.h"
39 #include "ui/gfx/font_list.h"
40 #include "ui/gfx/geometry/point.h"
41 #include "ui/gfx/path.h"
42 #include "ui/gfx/skia_util.h"
43 #include "ui/views/background.h"
44 #include "ui/views/border.h"
45 #include "ui/views/bubble/bubble_border.h"
46 #include "ui/views/bubble/bubble_frame_view.h"
47 #include "ui/views/controls/button/blue_button.h"
48 #include "ui/views/controls/button/checkbox.h"
49 #include "ui/views/controls/button/label_button.h"
50 #include "ui/views/controls/button/label_button_border.h"
51 #include "ui/views/controls/button/menu_button.h"
52 #include "ui/views/controls/combobox/combobox.h"
53 #include "ui/views/controls/image_view.h"
54 #include "ui/views/controls/label.h"
55 #include "ui/views/controls/link.h"
56 #include "ui/views/controls/menu/menu_runner.h"
57 #include "ui/views/controls/separator.h"
58 #include "ui/views/controls/styled_label.h"
59 #include "ui/views/controls/textfield/textfield.h"
60 #include "ui/views/controls/webview/webview.h"
61 #include "ui/views/layout/box_layout.h"
62 #include "ui/views/layout/fill_layout.h"
63 #include "ui/views/layout/grid_layout.h"
64 #include "ui/views/layout/layout_constants.h"
65 #include "ui/views/painter.h"
66 #include "ui/views/view_targeter.h"
67 #include "ui/views/widget/widget.h"
68 #include "ui/views/window/dialog_client_view.h"
69 #include "ui/views/window/non_client_view.h"
70
71 namespace autofill {
72
73 namespace {
74
75 // The width for the section container.
76 const int kSectionContainerWidth = 440;
77
78 // The minimum useful height of the contents area of the dialog.
79 const int kMinimumContentsHeight = 101;
80
81 // Horizontal padding between text and other elements (in pixels).
82 const int kAroundTextPadding = 4;
83
84 // The space between the edges of a notification bar and the text within (in
85 // pixels).
86 const int kNotificationPadding = 17;
87
88 // Vertical padding above and below each detail section (in pixels).
89 const int kDetailSectionVerticalPadding = 10;
90
91 const int kArrowHeight = 7;
92 const int kArrowWidth = 2 * kArrowHeight;
93
94 // The padding inside the edges of the dialog, in pixels.
95 const int kDialogEdgePadding = 20;
96
97 // The vertical padding between rows of manual inputs (in pixels).
98 const int kManualInputRowPadding = 10;
99
100 // The top and bottom padding, in pixels, for the suggestions menu dropdown
101 // arrows.
102 const int kMenuButtonTopInset = 3;
103 const int kMenuButtonBottomInset = 6;
104
105 const char kNotificationAreaClassName[] = "autofill/NotificationArea";
106 const char kSectionContainerClassName[] = "autofill/SectionContainer";
107 const char kSuggestedButtonClassName[] = "autofill/SuggestedButton";
108
109 // Draws an arrow at the top of |canvas| pointing to |tip_x|.
110 void DrawArrow(gfx::Canvas* canvas,
111 int tip_x,
112 const SkColor& fill_color,
113 const SkColor& stroke_color) {
114 const int arrow_half_width = kArrowWidth / 2.0f;
115
116 SkPath arrow;
117 arrow.moveTo(tip_x - arrow_half_width, kArrowHeight);
118 arrow.lineTo(tip_x, 0);
119 arrow.lineTo(tip_x + arrow_half_width, kArrowHeight);
120
121 SkPaint fill_paint;
122 fill_paint.setColor(fill_color);
123 canvas->DrawPath(arrow, fill_paint);
124
125 if (stroke_color != SK_ColorTRANSPARENT) {
126 SkPaint stroke_paint;
127 stroke_paint.setColor(stroke_color);
128 stroke_paint.setStyle(SkPaint::kStroke_Style);
129 canvas->DrawPath(arrow, stroke_paint);
130 }
131 }
132
133 void SelectComboboxValueOrSetToDefault(views::Combobox* combobox,
134 const base::string16& value) {
135 if (!combobox->SelectValue(value))
136 combobox->SetSelectedIndex(combobox->model()->GetDefaultIndex());
137 }
138
139 // This class handles layout for the first row of a SuggestionView.
140 // It exists to circumvent shortcomings of GridLayout and BoxLayout (namely that
141 // the former doesn't fully respect child visibility, and that the latter won't
142 // expand a single child).
143 class SectionRowView : public views::View {
144 public:
145 SectionRowView() { SetBorder(views::Border::CreateEmptyBorder(10, 0, 0, 0)); }
146
147 ~SectionRowView() override {}
148
149 // views::View implementation:
150 gfx::Size GetPreferredSize() const override {
151 int height = 0;
152 int width = 0;
153 for (int i = 0; i < child_count(); ++i) {
154 if (child_at(i)->visible()) {
155 if (width > 0)
156 width += kAroundTextPadding;
157
158 gfx::Size size = child_at(i)->GetPreferredSize();
159 height = std::max(height, size.height());
160 width += size.width();
161 }
162 }
163
164 gfx::Insets insets = GetInsets();
165 return gfx::Size(width + insets.width(), height + insets.height());
166 }
167
168 void Layout() override {
169 const gfx::Rect bounds = GetContentsBounds();
170
171 // Icon is left aligned.
172 int start_x = bounds.x();
173 views::View* icon = child_at(0);
174 if (icon->visible()) {
175 icon->SizeToPreferredSize();
176 icon->SetX(start_x);
177 icon->SetY(bounds.y() +
178 (bounds.height() - icon->bounds().height()) / 2);
179 start_x += icon->bounds().width() + kAroundTextPadding;
180 }
181
182 // Textfield is right aligned.
183 int end_x = bounds.width();
184 views::View* textfield = child_at(2);
185 if (textfield->visible()) {
186 const int preferred_width = textfield->GetPreferredSize().width();
187 textfield->SetBounds(bounds.width() - preferred_width, bounds.y(),
188 preferred_width, bounds.height());
189 end_x = textfield->bounds().x() - kAroundTextPadding;
190 }
191
192 // Label takes up all the space in between.
193 views::View* label = child_at(1);
194 if (label->visible())
195 label->SetBounds(start_x, bounds.y(), end_x - start_x, bounds.height());
196
197 views::View::Layout();
198 }
199
200 private:
201 DISALLOW_COPY_AND_ASSIGN(SectionRowView);
202 };
203
204 // A view that propagates visibility and preferred size changes.
205 class LayoutPropagationView : public views::View {
206 public:
207 LayoutPropagationView() {}
208 ~LayoutPropagationView() override {}
209
210 protected:
211 void ChildVisibilityChanged(views::View* child) override {
212 PreferredSizeChanged();
213 }
214 void ChildPreferredSizeChanged(views::View* child) override {
215 PreferredSizeChanged();
216 }
217
218 private:
219 DISALLOW_COPY_AND_ASSIGN(LayoutPropagationView);
220 };
221
222 // A View for a single notification banner.
223 class NotificationView : public views::View,
224 public views::StyledLabelListener {
225 public:
226 NotificationView(const DialogNotification& data,
227 AutofillDialogViewDelegate* delegate)
228 : data_(data),
229 delegate_(delegate),
230 checkbox_(NULL) {
231 std::unique_ptr<views::View> label_view;
232 std::unique_ptr<views::StyledLabel> label(
233 new views::StyledLabel(data.display_text(), this));
234 label->set_auto_color_readability_enabled(false);
235
236 views::StyledLabel::RangeStyleInfo text_style;
237 text_style.color = data.GetTextColor();
238
239 if (data.link_range().is_empty()) {
240 label->AddStyleRange(gfx::Range(0, data.display_text().size()),
241 text_style);
242 } else {
243 gfx::Range prefix_range(0, data.link_range().start());
244 if (!prefix_range.is_empty())
245 label->AddStyleRange(prefix_range, text_style);
246
247 label->AddStyleRange(data.link_range(),
248 views::StyledLabel::RangeStyleInfo::CreateForLink());
249
250 gfx::Range suffix_range(data.link_range().end(),
251 data.display_text().size());
252 if (!suffix_range.is_empty())
253 label->AddStyleRange(suffix_range, text_style);
254 }
255 label_view.reset(label.release());
256
257 AddChildView(label_view.release());
258
259 if (!data.tooltip_text().empty())
260 AddChildView(new TooltipIcon(data.tooltip_text()));
261
262 set_background(
263 views::Background::CreateSolidBackground(data.GetBackgroundColor()));
264 SetBorder(views::Border::CreateSolidSidedBorder(
265 1, 0, 1, 0, data.GetBorderColor()));
266 }
267
268 ~NotificationView() override {}
269
270 views::Checkbox* checkbox() {
271 return checkbox_;
272 }
273
274 // views::View implementation.
275 gfx::Insets GetInsets() const override {
276 int vertical_padding = kNotificationPadding;
277 if (checkbox_)
278 vertical_padding -= 3;
279 return gfx::Insets(vertical_padding, kDialogEdgePadding,
280 vertical_padding, kDialogEdgePadding);
281 }
282
283 int GetHeightForWidth(int width) const override {
284 int label_width = width - GetInsets().width();
285 if (child_count() > 1) {
286 const views::View* tooltip_icon = child_at(1);
287 label_width -= tooltip_icon->GetPreferredSize().width() +
288 kDialogEdgePadding;
289 }
290
291 return child_at(0)->GetHeightForWidth(label_width) + GetInsets().height();
292 }
293
294 void Layout() override {
295 // Surprisingly, GetContentsBounds() doesn't consult GetInsets().
296 gfx::Rect bounds = GetLocalBounds();
297 bounds.Inset(GetInsets());
298 int right_bound = bounds.right();
299
300 if (child_count() > 1) {
301 // The icon takes up the entire vertical space and an extra 20px on
302 // each side. This increases the hover target for the tooltip.
303 views::View* tooltip_icon = child_at(1);
304 gfx::Size icon_size = tooltip_icon->GetPreferredSize();
305 int icon_width = icon_size.width() + kDialogEdgePadding;
306 right_bound -= icon_width;
307 tooltip_icon->SetBounds(
308 right_bound, 0,
309 icon_width + kDialogEdgePadding, GetLocalBounds().height());
310 }
311
312 child_at(0)->SetBounds(bounds.x(), bounds.y(),
313 right_bound - bounds.x(), bounds.height());
314 }
315
316 // views::StyledLabelListener implementation.
317 void StyledLabelLinkClicked(views::StyledLabel* label,
318 const gfx::Range& range,
319 int event_flags) override {
320 delegate_->LinkClicked(data_.link_url());
321 }
322
323 private:
324 // The model data for this notification.
325 DialogNotification data_;
326
327 // The delegate that handles interaction with |this|.
328 AutofillDialogViewDelegate* delegate_;
329
330 // The checkbox associated with this notification, or NULL if there is none.
331 views::Checkbox* checkbox_;
332
333 DISALLOW_COPY_AND_ASSIGN(NotificationView);
334 };
335
336 // Gets either the Combobox or ExpandingTextfield that is an ancestor (including
337 // self) of |view|.
338 views::View* GetAncestralInputView(views::View* view) {
339 if (view->GetClassName() == views::Combobox::kViewClassName)
340 return view;
341
342 return view->GetAncestorWithClassName(ExpandingTextfield::kViewClassName);
343 }
344
345 // A class that informs |delegate_| when an unhandled mouse press occurs.
346 class MousePressedHandler : public ui::EventHandler {
347 public:
348 explicit MousePressedHandler(AutofillDialogViewDelegate* delegate)
349 : delegate_(delegate) {}
350
351 // ui::EventHandler implementation.
352 void OnMouseEvent(ui::MouseEvent* event) override {
353 if (event->type() == ui::ET_MOUSE_PRESSED && !event->handled())
354 delegate_->FocusMoved();
355 }
356
357 private:
358 AutofillDialogViewDelegate* const delegate_;
359
360 DISALLOW_COPY_AND_ASSIGN(MousePressedHandler);
361 };
362
363 } // namespace
364
365 // AutofillDialogViews::NotificationArea ---------------------------------------
366
367 AutofillDialogViews::NotificationArea::NotificationArea(
368 AutofillDialogViewDelegate* delegate)
369 : delegate_(delegate) {
370 // Reserve vertical space for the arrow (regardless of whether one exists).
371 // The -1 accounts for the border.
372 SetBorder(views::Border::CreateEmptyBorder(kArrowHeight - 1, 0, 0, 0));
373
374 views::BoxLayout* box_layout =
375 new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0);
376 SetLayoutManager(box_layout);
377 }
378
379 AutofillDialogViews::NotificationArea::~NotificationArea() {}
380
381 void AutofillDialogViews::NotificationArea::SetNotifications(
382 const std::vector<DialogNotification>& notifications) {
383 notifications_ = notifications;
384
385 RemoveAllChildViews(true);
386
387 if (notifications_.empty())
388 return;
389
390 for (size_t i = 0; i < notifications_.size(); ++i) {
391 const DialogNotification& notification = notifications_[i];
392 std::unique_ptr<NotificationView> view(
393 new NotificationView(notification, delegate_));
394
395 AddChildView(view.release());
396 }
397
398 PreferredSizeChanged();
399 }
400
401 gfx::Size AutofillDialogViews::NotificationArea::GetPreferredSize() const {
402 gfx::Size size = views::View::GetPreferredSize();
403 // Ensure that long notifications wrap and don't enlarge the dialog.
404 size.set_width(1);
405 return size;
406 }
407
408 const char* AutofillDialogViews::NotificationArea::GetClassName() const {
409 return kNotificationAreaClassName;
410 }
411
412 void AutofillDialogViews::NotificationArea::PaintChildren(
413 const ui::PaintContext& context) {
414 views::View::PaintChildren(context);
415 if (HasArrow()) {
416 ui::PaintRecorder recorder(context, size());
417 DrawArrow(
418 recorder.canvas(),
419 GetMirroredXInView(width() - arrow_centering_anchor_->width() / 2.0f),
420 notifications_[0].GetBackgroundColor(),
421 notifications_[0].GetBorderColor());
422 }
423 }
424
425 void AutofillDialogViews::OnWidgetDestroying(views::Widget* widget) {
426 if (widget == window_)
427 window_->GetRootView()->RemovePostTargetHandler(event_handler_.get());
428 }
429
430 void AutofillDialogViews::OnWidgetClosing(views::Widget* widget) {
431 observer_.Remove(widget);
432 if (error_bubble_ && error_bubble_->GetWidget() == widget)
433 error_bubble_ = NULL;
434 }
435
436 void AutofillDialogViews::OnWidgetBoundsChanged(views::Widget* widget,
437 const gfx::Rect& new_bounds) {
438 if (error_bubble_ && error_bubble_->GetWidget() == widget)
439 return;
440 HideErrorBubble();
441 }
442
443 bool AutofillDialogViews::NotificationArea::HasArrow() {
444 return !notifications_.empty() && notifications_[0].HasArrow() &&
445 arrow_centering_anchor_.get();
446 }
447
448 // AutofillDialogViews::SectionContainer ---------------------------------------
449
450 AutofillDialogViews::SectionContainer::SectionContainer(
451 const base::string16& label,
452 views::View* controls,
453 views::Button* proxy_button)
454 : proxy_button_(proxy_button),
455 forward_mouse_events_(false) {
456 set_notify_enter_exit_on_child(true);
457
458 SetBorder(views::Border::CreateEmptyBorder(kDetailSectionVerticalPadding,
459 kDialogEdgePadding,
460 kDetailSectionVerticalPadding,
461 kDialogEdgePadding));
462
463 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
464 views::Label* label_view = new views::Label(
465 label, rb.GetFontList(ui::ResourceBundle::BoldFont));
466 label_view->SetHorizontalAlignment(gfx::ALIGN_LEFT);
467
468 views::View* label_bar = new views::View();
469 views::GridLayout* label_bar_layout = new views::GridLayout(label_bar);
470 label_bar->SetLayoutManager(label_bar_layout);
471 const int kColumnSetId = 0;
472 views::ColumnSet* columns = label_bar_layout->AddColumnSet(kColumnSetId);
473 columns->AddColumn(
474 views::GridLayout::LEADING,
475 views::GridLayout::LEADING,
476 0,
477 views::GridLayout::FIXED,
478 kSectionContainerWidth - proxy_button->GetPreferredSize().width(),
479 0);
480 columns->AddColumn(views::GridLayout::LEADING,
481 views::GridLayout::LEADING,
482 0,
483 views::GridLayout::USE_PREF,
484 0,
485 0);
486 label_bar_layout->StartRow(0, kColumnSetId);
487 label_bar_layout->AddView(label_view);
488 label_bar_layout->AddView(proxy_button);
489
490 SetLayoutManager(new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0));
491 AddChildView(label_bar);
492 AddChildView(controls);
493
494 SetEventTargeter(
495 std::unique_ptr<views::ViewTargeter>(new views::ViewTargeter(this)));
496 }
497
498 AutofillDialogViews::SectionContainer::~SectionContainer() {}
499
500 void AutofillDialogViews::SectionContainer::SetActive(bool active) {
501 bool is_active = active && proxy_button_->visible();
502 if (is_active == !!background())
503 return;
504
505 set_background(
506 is_active ? views::Background::CreateSolidBackground(kLightShadingColor)
507 : NULL);
508 SchedulePaint();
509 }
510
511 void AutofillDialogViews::SectionContainer::SetForwardMouseEvents(
512 bool forward) {
513 forward_mouse_events_ = forward;
514 if (!forward)
515 set_background(NULL);
516 }
517
518 const char* AutofillDialogViews::SectionContainer::GetClassName() const {
519 return kSectionContainerClassName;
520 }
521
522 void AutofillDialogViews::SectionContainer::OnMouseMoved(
523 const ui::MouseEvent& event) {
524 SetActive(ShouldForwardEvent(event));
525 }
526
527 void AutofillDialogViews::SectionContainer::OnMouseEntered(
528 const ui::MouseEvent& event) {
529 if (!ShouldForwardEvent(event))
530 return;
531
532 SetActive(true);
533 proxy_button_->OnMouseEntered(ProxyEvent(event));
534 SchedulePaint();
535 }
536
537 void AutofillDialogViews::SectionContainer::OnMouseExited(
538 const ui::MouseEvent& event) {
539 SetActive(false);
540 if (!ShouldForwardEvent(event))
541 return;
542
543 proxy_button_->OnMouseExited(ProxyEvent(event));
544 SchedulePaint();
545 }
546
547 bool AutofillDialogViews::SectionContainer::OnMousePressed(
548 const ui::MouseEvent& event) {
549 if (!ShouldForwardEvent(event))
550 return false;
551
552 return proxy_button_->OnMousePressed(ProxyEvent(event));
553 }
554
555 void AutofillDialogViews::SectionContainer::OnMouseReleased(
556 const ui::MouseEvent& event) {
557 if (!ShouldForwardEvent(event))
558 return;
559
560 proxy_button_->OnMouseReleased(ProxyEvent(event));
561 }
562
563 void AutofillDialogViews::SectionContainer::OnGestureEvent(
564 ui::GestureEvent* event) {
565 if (!ShouldForwardEvent(*event))
566 return;
567
568 proxy_button_->OnGestureEvent(event);
569 }
570
571 views::View* AutofillDialogViews::SectionContainer::TargetForRect(
572 views::View* root,
573 const gfx::Rect& rect) {
574 CHECK_EQ(root, this);
575 views::View* handler = views::ViewTargeterDelegate::TargetForRect(root, rect);
576
577 // If the event is not in the label bar and there's no background to be
578 // cleared, let normal event handling take place.
579 if (!background() &&
580 rect.CenterPoint().y() > child_at(0)->bounds().bottom()) {
581 return handler;
582 }
583
584 // Special case for (CVC) inputs in the suggestion view.
585 if (forward_mouse_events_ &&
586 handler->GetAncestorWithClassName(ExpandingTextfield::kViewClassName)) {
587 return handler;
588 }
589
590 // Special case for the proxy button itself.
591 if (handler == proxy_button_)
592 return handler;
593
594 return this;
595 }
596
597 // static
598 ui::MouseEvent AutofillDialogViews::SectionContainer::ProxyEvent(
599 const ui::MouseEvent& event) {
600 ui::MouseEvent event_copy = event;
601 event_copy.set_location(gfx::Point());
602 return event_copy;
603 }
604
605 bool AutofillDialogViews::SectionContainer::ShouldForwardEvent(
606 const ui::LocatedEvent& event) {
607 // Always forward events on the label bar.
608 return forward_mouse_events_ || event.y() <= child_at(0)->bounds().bottom();
609 }
610
611 // AutofillDialogViews::SuggestedButton ----------------------------------------
612
613 AutofillDialogViews::SuggestedButton::SuggestedButton(
614 views::MenuButtonListener* listener)
615 : views::MenuButton(base::string16(), listener, false) {
616 const int kFocusBorderWidth = 1;
617 SetBorder(views::Border::CreateEmptyBorder(kMenuButtonTopInset,
618 kFocusBorderWidth,
619 kMenuButtonBottomInset,
620 kFocusBorderWidth));
621 gfx::Insets insets = GetInsets();
622 insets += gfx::Insets(-kFocusBorderWidth, -kFocusBorderWidth,
623 -kFocusBorderWidth, -kFocusBorderWidth);
624 SetFocusPainter(
625 views::Painter::CreateDashedFocusPainterWithInsets(insets));
626 SetFocusBehavior(FocusBehavior::ALWAYS);
627 }
628
629 AutofillDialogViews::SuggestedButton::~SuggestedButton() {}
630
631 gfx::Size AutofillDialogViews::SuggestedButton::GetPreferredSize() const {
632 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
633 gfx::Size size = rb.GetImageNamed(ResourceIDForState()).Size();
634 const gfx::Insets insets = GetInsets();
635 size.Enlarge(insets.width(), insets.height());
636 return size;
637 }
638
639 const char* AutofillDialogViews::SuggestedButton::GetClassName() const {
640 return kSuggestedButtonClassName;
641 }
642
643 void AutofillDialogViews::SuggestedButton::OnPaint(gfx::Canvas* canvas) {
644 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
645 const gfx::Insets insets = GetInsets();
646 canvas->DrawImageInt(*rb.GetImageSkiaNamed(ResourceIDForState()),
647 insets.left(), insets.top());
648 views::Painter::PaintFocusPainter(this, canvas, focus_painter());
649 }
650
651 int AutofillDialogViews::SuggestedButton::ResourceIDForState() const {
652 views::Button::ButtonState button_state = state();
653 if (button_state == views::Button::STATE_PRESSED)
654 return IDR_AUTOFILL_DIALOG_MENU_BUTTON_P;
655 else if (button_state == views::Button::STATE_HOVERED)
656 return IDR_AUTOFILL_DIALOG_MENU_BUTTON_H;
657 else if (button_state == views::Button::STATE_DISABLED)
658 return IDR_AUTOFILL_DIALOG_MENU_BUTTON_D;
659 DCHECK_EQ(views::Button::STATE_NORMAL, button_state);
660 return IDR_AUTOFILL_DIALOG_MENU_BUTTON;
661 }
662
663 // AutofillDialogViews::DetailsContainerView -----------------------------------
664
665 AutofillDialogViews::DetailsContainerView::DetailsContainerView(
666 const base::Closure& callback)
667 : bounds_changed_callback_(callback),
668 ignore_layouts_(false) {}
669
670 AutofillDialogViews::DetailsContainerView::~DetailsContainerView() {}
671
672 void AutofillDialogViews::DetailsContainerView::OnBoundsChanged(
673 const gfx::Rect& previous_bounds) {
674 bounds_changed_callback_.Run();
675 }
676
677 void AutofillDialogViews::DetailsContainerView::Layout() {
678 if (!ignore_layouts_)
679 views::View::Layout();
680 }
681
682 // AutofillDialogViews::SuggestionView -----------------------------------------
683
684 AutofillDialogViews::SuggestionView::SuggestionView(
685 AutofillDialogViews* autofill_dialog)
686 : label_(new views::Label()),
687 label_line_2_(new views::Label()),
688 icon_(new views::ImageView()),
689 textfield_(
690 new ExpandingTextfield(base::string16(),
691 base::string16(),
692 false,
693 autofill_dialog)) {
694 // TODO(estade): Make this the correct color.
695 SetBorder(views::Border::CreateSolidSidedBorder(1, 0, 0, 0, SK_ColorLTGRAY));
696
697 SectionRowView* label_container = new SectionRowView();
698 AddChildView(label_container);
699
700 // Label and icon.
701 label_container->AddChildView(icon_);
702 label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
703 label_container->AddChildView(label_);
704
705 // TODO(estade): get the sizing and spacing right on this textfield.
706 textfield_->SetVisible(false);
707 textfield_->SetDefaultWidthInCharacters(15);
708 label_container->AddChildView(textfield_);
709
710 label_line_2_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
711 label_line_2_->SetVisible(false);
712 label_line_2_->SetLineHeight(22);
713 label_line_2_->SetMultiLine(true);
714 AddChildView(label_line_2_);
715
716 SetLayoutManager(new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 7));
717 }
718
719 AutofillDialogViews::SuggestionView::~SuggestionView() {}
720
721 gfx::Size AutofillDialogViews::SuggestionView::GetPreferredSize() const {
722 // There's no preferred width. The parent's layout should get the preferred
723 // height from GetHeightForWidth().
724 return gfx::Size();
725 }
726
727 int AutofillDialogViews::SuggestionView::GetHeightForWidth(int width) const {
728 int height = 0;
729 CanUseVerticallyCompactText(width, &height);
730 return height;
731 }
732
733 bool AutofillDialogViews::SuggestionView::CanUseVerticallyCompactText(
734 int available_width,
735 int* resulting_height) const {
736 // This calculation may be costly, avoid doing it more than once per width.
737 if (!calculated_heights_.count(available_width)) {
738 // Changing the state of |this| now will lead to extra layouts and
739 // paints we don't want, so create another SuggestionView to calculate
740 // which label we have room to show.
741 SuggestionView sizing_view(NULL);
742 sizing_view.SetLabelText(state_.vertically_compact_text);
743 sizing_view.SetIcon(state_.icon);
744 sizing_view.SetTextfield(state_.extra_text, state_.extra_icon);
745 sizing_view.label_->SetSize(gfx::Size(available_width, 0));
746 sizing_view.label_line_2_->SetSize(gfx::Size(available_width, 0));
747
748 // Shortcut |sizing_view|'s GetHeightForWidth() to avoid an infinite loop.
749 // Its BoxLayout must do these calculations for us.
750 views::LayoutManager* layout = sizing_view.GetLayoutManager();
751 if (layout->GetPreferredSize(&sizing_view).width() <= available_width) {
752 calculated_heights_[available_width] = std::make_pair(
753 true,
754 layout->GetPreferredHeightForWidth(&sizing_view, available_width));
755 } else {
756 sizing_view.SetLabelText(state_.horizontally_compact_text);
757 calculated_heights_[available_width] = std::make_pair(
758 false,
759 layout->GetPreferredHeightForWidth(&sizing_view, available_width));
760 }
761 }
762
763 const std::pair<bool, int>& values = calculated_heights_[available_width];
764 *resulting_height = values.second;
765 return values.first;
766 }
767
768 void AutofillDialogViews::SuggestionView::OnBoundsChanged(
769 const gfx::Rect& previous_bounds) {
770 UpdateLabelText();
771 }
772
773 void AutofillDialogViews::SuggestionView::SetState(
774 const SuggestionState& state) {
775 calculated_heights_.clear();
776 state_ = state;
777 SetVisible(state_.visible);
778 UpdateLabelText();
779 SetIcon(state_.icon);
780 SetTextfield(state_.extra_text, state_.extra_icon);
781 PreferredSizeChanged();
782 }
783
784 void AutofillDialogViews::SuggestionView::SetLabelText(
785 const base::string16& text) {
786 // TODO(estade): does this localize well?
787 base::string16 line_return(base::ASCIIToUTF16("\n"));
788 size_t position = text.find(line_return);
789 if (position == base::string16::npos) {
790 label_->SetText(text);
791 label_line_2_->SetVisible(false);
792 } else {
793 label_->SetText(text.substr(0, position));
794 label_line_2_->SetText(text.substr(position + line_return.length()));
795 label_line_2_->SetVisible(true);
796 }
797 }
798
799 void AutofillDialogViews::SuggestionView::SetIcon(
800 const gfx::Image& image) {
801 icon_->SetVisible(!image.IsEmpty());
802 icon_->SetImage(image.AsImageSkia());
803 }
804
805 void AutofillDialogViews::SuggestionView::SetTextfield(
806 const base::string16& placeholder_text,
807 const gfx::Image& icon) {
808 textfield_->SetPlaceholderText(placeholder_text);
809 textfield_->SetIcon(icon);
810 textfield_->SetVisible(!placeholder_text.empty());
811 }
812
813 void AutofillDialogViews::SuggestionView::UpdateLabelText() {
814 int unused;
815 SetLabelText(CanUseVerticallyCompactText(width(), &unused) ?
816 state_.vertically_compact_text :
817 state_.horizontally_compact_text);
818 }
819
820 // AutofillDialogView ----------------------------------------------------------
821
822 // static
823 AutofillDialogView* AutofillDialogView::Create(
824 AutofillDialogViewDelegate* delegate) {
825 return new AutofillDialogViews(delegate);
826 }
827
828 // AutofillDialogViews ---------------------------------------------------------
829
830 AutofillDialogViews::AutofillDialogViews(AutofillDialogViewDelegate* delegate)
831 : delegate_(delegate),
832 updates_scope_(0),
833 needs_update_(false),
834 window_(NULL),
835 notification_area_(NULL),
836 scrollable_area_(NULL),
837 details_container_(NULL),
838 button_strip_extra_view_(NULL),
839 save_in_chrome_checkbox_(NULL),
840 save_in_chrome_checkbox_container_(NULL),
841 focus_manager_(NULL),
842 error_bubble_(NULL),
843 observer_(this) {
844 DCHECK(delegate);
845 detail_groups_.insert(std::make_pair(SECTION_CC,
846 DetailsGroup(SECTION_CC)));
847 detail_groups_.insert(std::make_pair(SECTION_BILLING,
848 DetailsGroup(SECTION_BILLING)));
849 detail_groups_.insert(std::make_pair(SECTION_SHIPPING,
850 DetailsGroup(SECTION_SHIPPING)));
851 }
852
853 AutofillDialogViews::~AutofillDialogViews() {
854 HideErrorBubble();
855 DCHECK(!window_);
856 }
857
858 void AutofillDialogViews::Show() {
859 InitChildViews();
860 UpdateNotificationArea();
861 UpdateButtonStripExtraView();
862
863 window_ = constrained_window::ShowWebModalDialogViews(
864 this, delegate_->GetWebContents());
865 focus_manager_ = window_->GetFocusManager();
866 focus_manager_->AddFocusChangeListener(this);
867
868 FocusInitialView();
869
870 // Listen for size changes on the browser.
871 views::Widget* browser_widget =
872 views::Widget::GetTopLevelWidgetForNativeView(
873 delegate_->GetWebContents()->GetNativeView());
874 observer_.Add(browser_widget);
875
876 // Listen for unhandled mouse presses on the non-client view.
877 event_handler_.reset(new MousePressedHandler(delegate_));
878 window_->GetRootView()->AddPostTargetHandler(event_handler_.get());
879 observer_.Add(window_);
880 }
881
882 void AutofillDialogViews::Hide() {
883 if (window_)
884 window_->Close();
885 }
886
887 void AutofillDialogViews::UpdatesStarted() {
888 updates_scope_++;
889 }
890
891 void AutofillDialogViews::UpdatesFinished() {
892 updates_scope_--;
893 DCHECK_GE(updates_scope_, 0);
894 if (updates_scope_ == 0 && needs_update_) {
895 needs_update_ = false;
896 ContentsPreferredSizeChanged();
897 }
898 }
899
900 void AutofillDialogViews::UpdateButtonStrip() {
901 button_strip_extra_view_->SetVisible(
902 GetDialogButtons() != ui::DIALOG_BUTTON_NONE);
903 UpdateButtonStripExtraView();
904 GetDialogClientView()->UpdateDialogButtons();
905
906 ContentsPreferredSizeChanged();
907 }
908
909 void AutofillDialogViews::UpdateDetailArea() {
910 scrollable_area_->SetVisible(true);
911 ContentsPreferredSizeChanged();
912 }
913
914 void AutofillDialogViews::UpdateForErrors() {
915 ValidateForm();
916 }
917
918 void AutofillDialogViews::UpdateNotificationArea() {
919 DCHECK(notification_area_);
920 notification_area_->SetNotifications(delegate_->CurrentNotifications());
921 ContentsPreferredSizeChanged();
922 }
923
924 void AutofillDialogViews::UpdateSection(DialogSection section) {
925 UpdateSectionImpl(section, true);
926 }
927
928 void AutofillDialogViews::UpdateErrorBubble() {
929 if (!delegate_->ShouldShowErrorBubble())
930 HideErrorBubble();
931 }
932
933 void AutofillDialogViews::FillSection(DialogSection section,
934 ServerFieldType originating_type) {
935 DetailsGroup* group = GroupForSection(section);
936 // Make sure to overwrite the originating input if it exists.
937 TextfieldMap::iterator text_mapping =
938 group->textfields.find(originating_type);
939 if (text_mapping != group->textfields.end())
940 text_mapping->second->SetText(base::string16());
941
942 // 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 == GetCreditCardSection() &&
946 AutofillType(originating_type).group() == CREDIT_CARD) {
947 for (ComboboxMap::const_iterator it = group->comboboxes.begin();
948 it != group->comboboxes.end(); ++it) {
949 if (AutofillType(it->first).group() == CREDIT_CARD)
950 it->second->SetSelectedIndex(it->second->model()->GetDefaultIndex());
951 }
952 }
953
954 UpdateSectionImpl(section, false);
955 }
956
957 void AutofillDialogViews::GetUserInput(DialogSection section,
958 FieldValueMap* output) {
959 DetailsGroup* group = GroupForSection(section);
960 for (TextfieldMap::const_iterator it = group->textfields.begin();
961 it != group->textfields.end(); ++it) {
962 output->insert(std::make_pair(it->first, it->second->GetText()));
963 }
964 for (ComboboxMap::const_iterator it = group->comboboxes.begin();
965 it != group->comboboxes.end(); ++it) {
966 output->insert(std::make_pair(it->first,
967 it->second->model()->GetItemAt(it->second->selected_index())));
968 }
969 }
970
971 base::string16 AutofillDialogViews::GetCvc() {
972 return GroupForSection(GetCreditCardSection())->suggested_info->
973 textfield()->GetText();
974 }
975
976 bool AutofillDialogViews::SaveDetailsLocally() {
977 DCHECK(save_in_chrome_checkbox_->visible());
978 return save_in_chrome_checkbox_->checked();
979 }
980
981 void AutofillDialogViews::ModelChanged() {
982 menu_runner_.reset();
983
984 for (DetailGroupMap::const_iterator iter = detail_groups_.begin();
985 iter != detail_groups_.end(); ++iter) {
986 UpdateDetailsGroupState(iter->second);
987 }
988 }
989
990 void AutofillDialogViews::ValidateSection(DialogSection section) {
991 ValidateGroup(*GroupForSection(section), VALIDATE_EDIT);
992 }
993
994 gfx::Size AutofillDialogViews::GetPreferredSize() const {
995 if (preferred_size_.IsEmpty())
996 preferred_size_ = CalculatePreferredSize(false);
997
998 return preferred_size_;
999 }
1000
1001 gfx::Size AutofillDialogViews::GetMinimumSize() const {
1002 return CalculatePreferredSize(true);
1003 }
1004
1005 void AutofillDialogViews::Layout() {
1006 const gfx::Rect content_bounds = GetContentsBounds();
1007 const int x = content_bounds.x();
1008 const int y = content_bounds.y();
1009 const int width = content_bounds.width();
1010 // Layout notification area at top of dialog.
1011 int notification_height = notification_area_->GetHeightForWidth(width);
1012 notification_area_->SetBounds(x, y, width, notification_height);
1013
1014 // The rest (the |scrollable_area_|) takes up whatever's left.
1015 if (scrollable_area_->visible()) {
1016 int scroll_y = y;
1017 if (notification_height > notification_area_->GetInsets().height())
1018 scroll_y += notification_height + views::kRelatedControlVerticalSpacing;
1019
1020 int scroll_bottom = content_bounds.bottom();
1021 DCHECK_EQ(scrollable_area_->contents(), details_container_);
1022 details_container_->SizeToPreferredSize();
1023 details_container_->Layout();
1024 // TODO(estade): remove this hack. See crbug.com/285996
1025 details_container_->set_ignore_layouts(true);
1026 scrollable_area_->SetBounds(x, scroll_y, width, scroll_bottom - scroll_y);
1027 details_container_->set_ignore_layouts(false);
1028 }
1029
1030 if (error_bubble_)
1031 error_bubble_->UpdatePosition();
1032 }
1033
1034 ui::ModalType AutofillDialogViews::GetModalType() const {
1035 return ui::MODAL_TYPE_CHILD;
1036 }
1037
1038 base::string16 AutofillDialogViews::GetWindowTitle() const {
1039 base::string16 title = delegate_->DialogTitle();
1040 // Hack alert: we don't want the dialog to jiggle when a title is added or
1041 // removed. Setting a non-empty string here keeps the dialog's title bar the
1042 // same size.
1043 return title.empty() ? base::ASCIIToUTF16(" ") : title;
1044 }
1045
1046 void AutofillDialogViews::WindowClosing() {
1047 focus_manager_->RemoveFocusChangeListener(this);
1048 }
1049
1050 void AutofillDialogViews::DeleteDelegate() {
1051 window_ = NULL;
1052 // |this| belongs to the controller (|delegate_|).
1053 delegate_->ViewClosed();
1054 }
1055
1056 int AutofillDialogViews::GetDialogButtons() const {
1057 return delegate_->GetDialogButtons();
1058 }
1059
1060 int AutofillDialogViews::GetDefaultDialogButton() const {
1061 if (GetDialogButtons() & ui::DIALOG_BUTTON_OK)
1062 return ui::DIALOG_BUTTON_OK;
1063
1064 return ui::DIALOG_BUTTON_NONE;
1065 }
1066
1067 base::string16 AutofillDialogViews::GetDialogButtonLabel(
1068 ui::DialogButton button) const {
1069 return button == ui::DIALOG_BUTTON_OK ?
1070 delegate_->ConfirmButtonText() : delegate_->CancelButtonText();
1071 }
1072
1073 bool AutofillDialogViews::ShouldDefaultButtonBeBlue() const {
1074 return true;
1075 }
1076
1077 bool AutofillDialogViews::IsDialogButtonEnabled(ui::DialogButton button) const {
1078 return delegate_->IsDialogButtonEnabled(button);
1079 }
1080
1081 views::View* AutofillDialogViews::GetInitiallyFocusedView() {
1082 if (!window_ || !focus_manager_)
1083 return NULL;
1084
1085 DCHECK(scrollable_area_->visible());
1086
1087 views::FocusManager* manager = focus_manager_;
1088 for (views::View* next = scrollable_area_;
1089 next;
1090 next = manager->GetNextFocusableView(next, window_, false, true)) {
1091 views::View* input_view = GetAncestralInputView(next);
1092 if (!input_view)
1093 continue;
1094
1095 // If there are no invalid inputs, return the first input found. Otherwise,
1096 // return the first invalid input found.
1097 if (validity_map_.empty() ||
1098 validity_map_.find(input_view) != validity_map_.end()) {
1099 return next;
1100 }
1101 }
1102
1103 return views::DialogDelegateView::GetInitiallyFocusedView();
1104 }
1105
1106 views::View* AutofillDialogViews::CreateExtraView() {
1107 return button_strip_extra_view_;
1108 }
1109
1110 bool AutofillDialogViews::Cancel() {
1111 delegate_->OnCancel();
1112 return true;
1113 }
1114
1115 bool AutofillDialogViews::Accept() {
1116 if (ValidateForm()) {
1117 delegate_->OnAccept();
1118 } else {
1119 // |ValidateForm()| failed; there should be invalid views in
1120 // |validity_map_|.
1121 DCHECK(!validity_map_.empty());
1122 FocusInitialView();
1123 }
1124
1125 return false;
1126 }
1127
1128 void AutofillDialogViews::ContentsChanged(views::Textfield* sender,
1129 const base::string16& new_contents) {
1130 InputEditedOrActivated(TypeForTextfield(sender),
1131 sender->GetBoundsInScreen(),
1132 true);
1133
1134 const ExpandingTextfield* expanding = static_cast<ExpandingTextfield*>(
1135 sender->GetAncestorWithClassName(ExpandingTextfield::kViewClassName));
1136 if (expanding && expanding->needs_layout())
1137 ContentsPreferredSizeChanged();
1138 }
1139
1140 bool AutofillDialogViews::HandleKeyEvent(views::Textfield* sender,
1141 const ui::KeyEvent& key_event) {
1142 content::NativeWebKeyboardEvent event(key_event);
1143 return delegate_->HandleKeyPressEventInInput(event);
1144 }
1145
1146 bool AutofillDialogViews::HandleMouseEvent(views::Textfield* sender,
1147 const ui::MouseEvent& mouse_event) {
1148 if (mouse_event.IsLeftMouseButton() && sender->HasFocus()) {
1149 InputEditedOrActivated(TypeForTextfield(sender),
1150 sender->GetBoundsInScreen(),
1151 false);
1152 // Show an error bubble if a user clicks on an input that's already focused
1153 // (and invalid).
1154 ShowErrorBubbleForViewIfNecessary(sender);
1155 }
1156
1157 return false;
1158 }
1159
1160 void AutofillDialogViews::OnWillChangeFocus(
1161 views::View* focused_before,
1162 views::View* focused_now) {
1163 delegate_->FocusMoved();
1164 HideErrorBubble();
1165 }
1166
1167 void AutofillDialogViews::OnDidChangeFocus(
1168 views::View* focused_before,
1169 views::View* focused_now) {
1170 // If user leaves an edit-field, revalidate the group it belongs to.
1171 if (focused_before) {
1172 DetailsGroup* group = GroupForView(focused_before);
1173 if (group && group->container->visible())
1174 ValidateGroup(*group, VALIDATE_EDIT);
1175 }
1176
1177 // Show an error bubble when the user focuses the input.
1178 if (focused_now) {
1179 focused_now->ScrollRectToVisible(focused_now->GetLocalBounds());
1180 ShowErrorBubbleForViewIfNecessary(focused_now);
1181 }
1182 }
1183
1184 void AutofillDialogViews::OnPerformAction(views::Combobox* combobox) {
1185 DialogSection section = GroupForView(combobox)->section;
1186 InputEditedOrActivated(TypeForCombobox(combobox), gfx::Rect(), true);
1187 // NOTE: |combobox| may have been deleted.
1188 ValidateGroup(*GroupForSection(section), VALIDATE_EDIT);
1189 }
1190
1191 void AutofillDialogViews::OnMenuButtonClicked(views::MenuButton* source,
1192 const gfx::Point& point,
1193 const ui::Event* event) {
1194 DCHECK_EQ(kSuggestedButtonClassName, source->GetClassName());
1195
1196 DetailsGroup* group = NULL;
1197 for (DetailGroupMap::iterator iter = detail_groups_.begin();
1198 iter != detail_groups_.end(); ++iter) {
1199 if (source == iter->second.suggested_button) {
1200 group = &iter->second;
1201 break;
1202 }
1203 }
1204 DCHECK(group);
1205
1206 if (!group->suggested_button->visible())
1207 return;
1208
1209 menu_runner_.reset(
1210 new views::MenuRunner(delegate_->MenuModelForSection(group->section), 0));
1211
1212 group->container->SetActive(true);
1213
1214 gfx::Rect screen_bounds = source->GetBoundsInScreen();
1215 screen_bounds.Inset(source->GetInsets());
1216 if (menu_runner_->RunMenuAt(source->GetWidget(),
1217 group->suggested_button,
1218 screen_bounds,
1219 views::MENU_ANCHOR_TOPRIGHT,
1220 ui::MENU_SOURCE_NONE) ==
1221 views::MenuRunner::MENU_DELETED) {
1222 return;
1223 }
1224
1225 group->container->SetActive(false);
1226 }
1227
1228 gfx::Size AutofillDialogViews::CalculatePreferredSize(
1229 bool get_minimum_size) const {
1230 gfx::Insets insets = GetInsets();
1231 gfx::Size scroll_size = scrollable_area_->contents()->GetPreferredSize();
1232 // The width is always set by the scroll area.
1233 const int width = scroll_size.width();
1234
1235 int height = 0;
1236 const int notification_height = notification_area_->GetHeightForWidth(width);
1237 if (notification_height > notification_area_->GetInsets().height())
1238 height += notification_height + views::kRelatedControlVerticalSpacing;
1239
1240 if (scrollable_area_->visible())
1241 height += get_minimum_size ? kMinimumContentsHeight : scroll_size.height();
1242
1243 return gfx::Size(width + insets.width(), height + insets.height());
1244 }
1245
1246 gfx::Size AutofillDialogViews::GetMinimumSignInViewSize() const {
1247 return gfx::Size(GetDialogClientView()->size().width() - GetInsets().width(),
1248 kMinimumContentsHeight);
1249 }
1250
1251 gfx::Size AutofillDialogViews::GetMaximumSignInViewSize() const {
1252 web_modal::WebContentsModalDialogHost* dialog_host =
1253 web_modal::WebContentsModalDialogManager::FromWebContents(
1254 delegate_->GetWebContents())->delegate()->
1255 GetWebContentsModalDialogHost();
1256
1257 // Inset the maximum dialog height to get the maximum content height.
1258 int height = dialog_host->GetMaximumDialogSize().height();
1259 const int non_client_height = GetWidget()->non_client_view()->height();
1260 const int client_height = GetWidget()->client_view()->height();
1261 // TODO(msw): Resolve the 12 pixel discrepancy; is that the bubble border?
1262 height -= non_client_height - client_height - 12;
1263 height = std::max(height, kMinimumContentsHeight);
1264
1265 // The dialog's width never changes.
1266 const int width = GetDialogClientView()->size().width() - GetInsets().width();
1267 return gfx::Size(width, height);
1268 }
1269
1270 // TODO(estade): remove.
1271 DialogSection AutofillDialogViews::GetCreditCardSection() const {
1272 return SECTION_CC;
1273 }
1274
1275 void AutofillDialogViews::InitChildViews() {
1276 button_strip_extra_view_ = new LayoutPropagationView();
1277 button_strip_extra_view_->SetLayoutManager(
1278 new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, 0));
1279
1280 save_in_chrome_checkbox_container_ = new views::View();
1281 save_in_chrome_checkbox_container_->SetLayoutManager(
1282 new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, 7));
1283 button_strip_extra_view_->AddChildView(save_in_chrome_checkbox_container_);
1284
1285 save_in_chrome_checkbox_ =
1286 new views::Checkbox(delegate_->SaveLocallyText());
1287 save_in_chrome_checkbox_->SetChecked(delegate_->ShouldSaveInChrome());
1288 save_in_chrome_checkbox_container_->AddChildView(save_in_chrome_checkbox_);
1289
1290 save_in_chrome_checkbox_container_->AddChildView(
1291 new TooltipIcon(delegate_->SaveLocallyTooltip()));
1292
1293 notification_area_ = new NotificationArea(delegate_);
1294 AddChildView(notification_area_);
1295
1296 scrollable_area_ = new views::ScrollView();
1297 scrollable_area_->set_hide_horizontal_scrollbar(true);
1298 scrollable_area_->SetContents(CreateDetailsContainer());
1299 AddChildView(scrollable_area_);
1300 }
1301
1302 views::View* AutofillDialogViews::CreateDetailsContainer() {
1303 details_container_ = new DetailsContainerView(
1304 base::Bind(&AutofillDialogViews::DetailsContainerBoundsChanged,
1305 base::Unretained(this)));
1306
1307 // A box layout is used because it respects widget visibility.
1308 details_container_->SetLayoutManager(
1309 new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0));
1310 for (DetailGroupMap::iterator iter = detail_groups_.begin();
1311 iter != detail_groups_.end(); ++iter) {
1312 CreateDetailsSection(iter->second.section);
1313 details_container_->AddChildView(iter->second.container);
1314 }
1315
1316 return details_container_;
1317 }
1318
1319 void AutofillDialogViews::CreateDetailsSection(DialogSection section) {
1320 // Inputs container (manual inputs + combobox).
1321 views::View* inputs_container = CreateInputsContainer(section);
1322
1323 DetailsGroup* group = GroupForSection(section);
1324 // Container (holds label + inputs).
1325 group->container = new SectionContainer(delegate_->LabelForSection(section),
1326 inputs_container,
1327 group->suggested_button);
1328 DCHECK(group->suggested_button->parent());
1329 UpdateDetailsGroupState(*group);
1330 }
1331
1332 views::View* AutofillDialogViews::CreateInputsContainer(DialogSection section) {
1333 // The |info_view| holds |manual_inputs| and |suggested_info|, allowing the
1334 // dialog to toggle which is shown.
1335 views::View* info_view = new views::View();
1336 info_view->SetLayoutManager(
1337 new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0));
1338
1339 DetailsGroup* group = GroupForSection(section);
1340 group->manual_input = new views::View();
1341 InitInputsView(section);
1342 info_view->AddChildView(group->manual_input);
1343
1344 group->suggested_info = new SuggestionView(this);
1345 info_view->AddChildView(group->suggested_info);
1346
1347 // TODO(estade): It might be slightly more OO if this button were created
1348 // and listened to by the section container.
1349 group->suggested_button = new SuggestedButton(this);
1350
1351 return info_view;
1352 }
1353
1354 // TODO(estade): we should be using Chrome-style constrained window padding
1355 // values.
1356 void AutofillDialogViews::InitInputsView(DialogSection section) {
1357 DetailsGroup* group = GroupForSection(section);
1358 EraseInvalidViewsInGroup(group);
1359
1360 TextfieldMap* textfields = &group->textfields;
1361 textfields->clear();
1362
1363 ComboboxMap* comboboxes = &group->comboboxes;
1364 comboboxes->clear();
1365
1366 views::View* view = group->manual_input;
1367 view->RemoveAllChildViews(true);
1368
1369 views::GridLayout* layout = new views::GridLayout(view);
1370 view->SetLayoutManager(layout);
1371
1372 int column_set_id = 0;
1373 const DetailInputs& inputs = delegate_->RequestedFieldsForSection(section);
1374 for (DetailInputs::const_iterator it = inputs.begin();
1375 it != inputs.end(); ++it) {
1376 const DetailInput& input = *it;
1377
1378 ui::ComboboxModel* input_model =
1379 delegate_->ComboboxModelForAutofillType(input.type);
1380 std::unique_ptr<views::View> view_to_add;
1381 if (input_model) {
1382 views::Combobox* combobox = new views::Combobox(input_model);
1383 combobox->set_listener(this);
1384 comboboxes->insert(std::make_pair(input.type, combobox));
1385 SelectComboboxValueOrSetToDefault(combobox, input.initial_value);
1386 view_to_add.reset(combobox);
1387 } else {
1388 ExpandingTextfield* field = new ExpandingTextfield(input.initial_value,
1389 input.placeholder_text,
1390 input.IsMultiline(),
1391 this);
1392 textfields->insert(std::make_pair(input.type, field));
1393 view_to_add.reset(field);
1394 }
1395
1396 if (input.length == DetailInput::NONE) {
1397 other_owned_views_.push_back(view_to_add.release());
1398 continue;
1399 }
1400
1401 if (input.length == DetailInput::LONG)
1402 ++column_set_id;
1403
1404 views::ColumnSet* column_set = layout->GetColumnSet(column_set_id);
1405 if (!column_set) {
1406 // Create a new column set and row.
1407 column_set = layout->AddColumnSet(column_set_id);
1408 if (it != inputs.begin())
1409 layout->AddPaddingRow(0, kManualInputRowPadding);
1410 layout->StartRow(0, column_set_id);
1411 } else {
1412 // Add a new column to existing row.
1413 column_set->AddPaddingColumn(0, views::kRelatedControlHorizontalSpacing);
1414 // Must explicitly skip the padding column since we've already started
1415 // adding views.
1416 layout->SkipColumns(1);
1417 }
1418
1419 float expand = input.expand_weight;
1420 column_set->AddColumn(views::GridLayout::FILL,
1421 views::GridLayout::FILL,
1422 expand ? expand : 1.0,
1423 views::GridLayout::USE_PREF,
1424 0,
1425 0);
1426
1427 // This is the same as AddView(view_to_add), except that 1 is used for the
1428 // view's preferred width. Thus the width of the column completely depends
1429 // on |expand|.
1430 layout->AddView(view_to_add.release(), 1, 1,
1431 views::GridLayout::FILL, views::GridLayout::FILL,
1432 1, 0);
1433
1434 if (input.length == DetailInput::LONG ||
1435 input.length == DetailInput::SHORT_EOL) {
1436 ++column_set_id;
1437 }
1438 }
1439
1440 SetIconsForSection(section);
1441 }
1442
1443 void AutofillDialogViews::UpdateSectionImpl(
1444 DialogSection section,
1445 bool clobber_inputs) {
1446 DetailsGroup* group = GroupForSection(section);
1447
1448 if (clobber_inputs) {
1449 ServerFieldType type = UNKNOWN_TYPE;
1450 views::View* focused = GetFocusManager()->GetFocusedView();
1451 if (focused && group->container->Contains(focused)) {
1452 // Remember which view was focused before the inputs are clobbered.
1453 if (focused->GetClassName() == ExpandingTextfield::kViewClassName)
1454 type = TypeForTextfield(focused);
1455 else if (focused->GetClassName() == views::Combobox::kViewClassName)
1456 type = TypeForCombobox(static_cast<views::Combobox*>(focused));
1457 }
1458
1459 InitInputsView(section);
1460
1461 if (type != UNKNOWN_TYPE) {
1462 // Restore the focus to the input with the previous type (e.g. country).
1463 views::View* to_focus = TextfieldForType(type);
1464 if (!to_focus) to_focus = ComboboxForType(type);
1465 if (to_focus)
1466 to_focus->RequestFocus();
1467 }
1468 } else {
1469 const DetailInputs& updated_inputs =
1470 delegate_->RequestedFieldsForSection(section);
1471
1472 for (DetailInputs::const_iterator iter = updated_inputs.begin();
1473 iter != updated_inputs.end(); ++iter) {
1474 const DetailInput& input = *iter;
1475
1476 TextfieldMap::iterator text_mapping = group->textfields.find(input.type);
1477 if (text_mapping != group->textfields.end()) {
1478 ExpandingTextfield* textfield = text_mapping->second;
1479 if (textfield->GetText().empty())
1480 textfield->SetText(input.initial_value);
1481 }
1482
1483 ComboboxMap::iterator combo_mapping = group->comboboxes.find(input.type);
1484 if (combo_mapping != group->comboboxes.end()) {
1485 views::Combobox* combobox = combo_mapping->second;
1486 if (combobox->selected_index() == combobox->model()->GetDefaultIndex())
1487 SelectComboboxValueOrSetToDefault(combobox, input.initial_value);
1488 }
1489 }
1490
1491 SetIconsForSection(section);
1492 }
1493
1494 UpdateDetailsGroupState(*group);
1495 }
1496
1497 void AutofillDialogViews::UpdateDetailsGroupState(const DetailsGroup& group) {
1498 const SuggestionState& suggestion_state =
1499 delegate_->SuggestionStateForSection(group.section);
1500 group.suggested_info->SetState(suggestion_state);
1501 group.manual_input->SetVisible(!suggestion_state.visible);
1502
1503 UpdateButtonStripExtraView();
1504
1505 const bool has_menu = !!delegate_->MenuModelForSection(group.section);
1506
1507 if (group.suggested_button)
1508 group.suggested_button->SetVisible(has_menu);
1509
1510 if (group.container) {
1511 group.container->SetForwardMouseEvents(
1512 has_menu && suggestion_state.visible);
1513 group.container->SetVisible(delegate_->SectionIsActive(group.section));
1514 if (group.container->visible())
1515 ValidateGroup(group, VALIDATE_EDIT);
1516 }
1517
1518 ContentsPreferredSizeChanged();
1519 }
1520
1521 void AutofillDialogViews::FocusInitialView() {
1522 views::View* to_focus = GetInitiallyFocusedView();
1523 if (to_focus && !to_focus->HasFocus())
1524 to_focus->RequestFocus();
1525 }
1526
1527 template<class T>
1528 void AutofillDialogViews::SetValidityForInput(
1529 T* input,
1530 const base::string16& message) {
1531 bool invalid = !message.empty();
1532 input->SetInvalid(invalid);
1533
1534 if (invalid) {
1535 validity_map_[input] = message;
1536 } else {
1537 validity_map_.erase(input);
1538
1539 if (error_bubble_ &&
1540 error_bubble_->anchor()->GetAncestorWithClassName(
1541 input->GetClassName()) == input) {
1542 validity_map_.erase(input);
1543 HideErrorBubble();
1544 }
1545 }
1546 }
1547
1548 void AutofillDialogViews::ShowErrorBubbleForViewIfNecessary(views::View* view) {
1549 if (!view->GetWidget())
1550 return;
1551
1552 if (!delegate_->ShouldShowErrorBubble()) {
1553 DCHECK(!error_bubble_);
1554 return;
1555 }
1556
1557 if (view->GetClassName() == DecoratedTextfield::kViewClassName &&
1558 !static_cast<DecoratedTextfield*>(view)->invalid()) {
1559 return;
1560 }
1561
1562 views::View* input_view = GetAncestralInputView(view);
1563 std::map<views::View*, base::string16>::iterator error_message =
1564 validity_map_.find(input_view);
1565 if (error_message != validity_map_.end()) {
1566 input_view->ScrollRectToVisible(input_view->GetLocalBounds());
1567
1568 if (!error_bubble_ || error_bubble_->anchor() != view) {
1569 HideErrorBubble();
1570 error_bubble_ = new InfoBubble(view, error_message->second);
1571 error_bubble_->set_align_to_anchor_edge(true);
1572 error_bubble_->set_preferred_width(
1573 (kSectionContainerWidth - views::kRelatedControlVerticalSpacing) / 2);
1574 bool show_above = view->GetClassName() == views::Combobox::kViewClassName;
1575 error_bubble_->set_show_above_anchor(show_above);
1576 error_bubble_->Show();
1577 observer_.Add(error_bubble_->GetWidget());
1578 }
1579 }
1580 }
1581
1582 void AutofillDialogViews::HideErrorBubble() {
1583 if (error_bubble_)
1584 error_bubble_->Hide();
1585 }
1586
1587 void AutofillDialogViews::MarkInputsInvalid(
1588 DialogSection section,
1589 const ValidityMessages& messages,
1590 bool overwrite_unsure) {
1591 DetailsGroup* group = GroupForSection(section);
1592 DCHECK(group->container->visible());
1593
1594 if (group->manual_input->visible()) {
1595 for (TextfieldMap::const_iterator iter = group->textfields.begin();
1596 iter != group->textfields.end(); ++iter) {
1597 const ValidityMessage& message =
1598 messages.GetMessageOrDefault(iter->first);
1599 if (overwrite_unsure || message.sure)
1600 SetValidityForInput(iter->second, message.text);
1601 }
1602 for (ComboboxMap::const_iterator iter = group->comboboxes.begin();
1603 iter != group->comboboxes.end(); ++iter) {
1604 const ValidityMessage& message =
1605 messages.GetMessageOrDefault(iter->first);
1606 if (overwrite_unsure || message.sure)
1607 SetValidityForInput(iter->second, message.text);
1608 }
1609 } else {
1610 EraseInvalidViewsInGroup(group);
1611
1612 if (section == GetCreditCardSection()) {
1613 // Special case CVC as it's not part of |group->manual_input|.
1614 const ValidityMessage& message =
1615 messages.GetMessageOrDefault(CREDIT_CARD_VERIFICATION_CODE);
1616 if (overwrite_unsure || message.sure) {
1617 SetValidityForInput(group->suggested_info->textfield(), message.text);
1618 }
1619 }
1620 }
1621 }
1622
1623 bool AutofillDialogViews::ValidateGroup(const DetailsGroup& group,
1624 ValidationType validation_type) {
1625 DCHECK(group.container->visible());
1626
1627 FieldValueMap detail_outputs;
1628
1629 if (group.manual_input->visible()) {
1630 for (TextfieldMap::const_iterator iter = group.textfields.begin();
1631 iter != group.textfields.end(); ++iter) {
1632 if (!iter->second->editable())
1633 continue;
1634
1635 detail_outputs[iter->first] = iter->second->GetText();
1636 }
1637 for (ComboboxMap::const_iterator iter = group.comboboxes.begin();
1638 iter != group.comboboxes.end(); ++iter) {
1639 if (!iter->second->enabled())
1640 continue;
1641
1642 views::Combobox* combobox = iter->second;
1643 base::string16 item =
1644 combobox->model()->GetItemAt(combobox->selected_index());
1645 detail_outputs[iter->first] = item;
1646 }
1647 } else if (group.section == GetCreditCardSection()) {
1648 ExpandingTextfield* cvc = group.suggested_info->textfield();
1649 if (cvc->visible())
1650 detail_outputs[CREDIT_CARD_VERIFICATION_CODE] = cvc->GetText();
1651 }
1652
1653 ValidityMessages validity = delegate_->InputsAreValid(group.section,
1654 detail_outputs);
1655 MarkInputsInvalid(group.section, validity, validation_type == VALIDATE_FINAL);
1656
1657 // If there are any validation errors, sure or unsure, the group is invalid.
1658 return !validity.HasErrors();
1659 }
1660
1661 bool AutofillDialogViews::ValidateForm() {
1662 bool all_valid = true;
1663 validity_map_.clear();
1664
1665 for (DetailGroupMap::iterator iter = detail_groups_.begin();
1666 iter != detail_groups_.end(); ++iter) {
1667 const DetailsGroup& group = iter->second;
1668 if (!group.container->visible())
1669 continue;
1670
1671 if (!ValidateGroup(group, VALIDATE_FINAL))
1672 all_valid = false;
1673 }
1674
1675 return all_valid;
1676 }
1677
1678 void AutofillDialogViews::InputEditedOrActivated(ServerFieldType type,
1679 const gfx::Rect& bounds,
1680 bool was_edit) {
1681 DCHECK_NE(UNKNOWN_TYPE, type);
1682
1683 ExpandingTextfield* textfield = TextfieldForType(type);
1684 views::Combobox* combobox = ComboboxForType(type);
1685
1686 // Both views may be NULL if the event comes from an inactive section, which
1687 // may occur when using an IME.
1688 if (!combobox && !textfield)
1689 return;
1690
1691 DCHECK_NE(!!combobox, !!textfield);
1692 DetailsGroup* group = textfield ? GroupForView(textfield) :
1693 GroupForView(combobox);
1694 base::string16 text = textfield ?
1695 textfield->GetText() :
1696 combobox->model()->GetItemAt(combobox->selected_index());
1697 DCHECK(group);
1698
1699 delegate_->UserEditedOrActivatedInput(group->section,
1700 type,
1701 GetWidget()->GetNativeView(),
1702 bounds,
1703 text,
1704 was_edit);
1705
1706 // If the field is a textfield and is invalid, check if the text is now valid.
1707 // Many fields (i.e. CC#) are invalid for most of the duration of editing,
1708 // so flagging them as invalid prematurely is not helpful. However,
1709 // correcting a minor mistake (i.e. a wrong CC digit) should immediately
1710 // result in validation - positive user feedback.
1711 if (textfield && textfield->invalid() && was_edit) {
1712 SetValidityForInput(
1713 textfield,
1714 delegate_->InputValidityMessage(
1715 group->section, type, textfield->GetText()));
1716
1717 // If the field transitioned from invalid to valid, re-validate the group,
1718 // since inter-field checks become meaningful with valid fields.
1719 if (!textfield->invalid())
1720 ValidateGroup(*group, VALIDATE_EDIT);
1721 }
1722
1723 if (delegate_->FieldControlsIcons(type))
1724 SetIconsForSection(group->section);
1725 }
1726
1727 void AutofillDialogViews::UpdateButtonStripExtraView() {
1728 save_in_chrome_checkbox_container_->SetVisible(
1729 delegate_->ShouldOfferToSaveInChrome());
1730 }
1731
1732 void AutofillDialogViews::ContentsPreferredSizeChanged() {
1733 if (updates_scope_ != 0) {
1734 needs_update_ = true;
1735 return;
1736 }
1737
1738 preferred_size_ = gfx::Size();
1739
1740 if (GetWidget() && delegate_ && delegate_->GetWebContents()) {
1741 constrained_window::UpdateWebContentsModalDialogPosition(
1742 GetWidget(),
1743 web_modal::WebContentsModalDialogManager::FromWebContents(
1744 delegate_->GetWebContents())->delegate()->
1745 GetWebContentsModalDialogHost());
1746 SetBoundsRect(bounds());
1747 }
1748 }
1749
1750 AutofillDialogViews::DetailsGroup* AutofillDialogViews::GroupForSection(
1751 DialogSection section) {
1752 return &detail_groups_.find(section)->second;
1753 }
1754
1755 AutofillDialogViews::DetailsGroup* AutofillDialogViews::GroupForView(
1756 views::View* view) {
1757 DCHECK(view);
1758
1759 views::View* input_view = GetAncestralInputView(view);
1760 if (!input_view)
1761 return NULL;
1762
1763 for (DetailGroupMap::iterator iter = detail_groups_.begin();
1764 iter != detail_groups_.end(); ++iter) {
1765 DetailsGroup* group = &iter->second;
1766 if (input_view->parent() == group->manual_input)
1767 return group;
1768
1769 // Textfields need to check a second case, since they can be suggested
1770 // inputs instead of directly editable inputs. Those are accessed via
1771 // |suggested_info|.
1772 if (input_view == group->suggested_info->textfield()) {
1773 return group;
1774 }
1775 }
1776
1777 return NULL;
1778 }
1779
1780 void AutofillDialogViews::EraseInvalidViewsInGroup(const DetailsGroup* group) {
1781 std::map<views::View*, base::string16>::iterator it = validity_map_.begin();
1782 while (it != validity_map_.end()) {
1783 if (GroupForView(it->first) == group)
1784 validity_map_.erase(it++);
1785 else
1786 ++it;
1787 }
1788 }
1789
1790 ExpandingTextfield* AutofillDialogViews::TextfieldForType(
1791 ServerFieldType type) {
1792 if (type == CREDIT_CARD_VERIFICATION_CODE) {
1793 DetailsGroup* group = GroupForSection(GetCreditCardSection());
1794 if (!group->manual_input->visible())
1795 return group->suggested_info->textfield();
1796 }
1797
1798 for (DetailGroupMap::iterator iter = detail_groups_.begin();
1799 iter != detail_groups_.end(); ++iter) {
1800 const DetailsGroup& group = iter->second;
1801 if (!delegate_->SectionIsActive(group.section))
1802 continue;
1803
1804 TextfieldMap::const_iterator text_mapping = group.textfields.find(type);
1805 if (text_mapping != group.textfields.end())
1806 return text_mapping->second;
1807 }
1808
1809 return NULL;
1810 }
1811
1812 ServerFieldType AutofillDialogViews::TypeForTextfield(
1813 const views::View* textfield) {
1814 const views::View* expanding =
1815 textfield->GetAncestorWithClassName(ExpandingTextfield::kViewClassName);
1816
1817 DetailsGroup* cc_group = GroupForSection(GetCreditCardSection());
1818 if (expanding == cc_group->suggested_info->textfield())
1819 return CREDIT_CARD_VERIFICATION_CODE;
1820
1821 for (DetailGroupMap::const_iterator it = detail_groups_.begin();
1822 it != detail_groups_.end(); ++it) {
1823 if (!delegate_->SectionIsActive(it->second.section))
1824 continue;
1825
1826 for (TextfieldMap::const_iterator text_it = it->second.textfields.begin();
1827 text_it != it->second.textfields.end(); ++text_it) {
1828 if (expanding == text_it->second)
1829 return text_it->first;
1830 }
1831 }
1832
1833 return UNKNOWN_TYPE;
1834 }
1835
1836 views::Combobox* AutofillDialogViews::ComboboxForType(
1837 ServerFieldType type) {
1838 for (DetailGroupMap::iterator iter = detail_groups_.begin();
1839 iter != detail_groups_.end(); ++iter) {
1840 const DetailsGroup& group = iter->second;
1841 if (!delegate_->SectionIsActive(group.section))
1842 continue;
1843
1844 ComboboxMap::const_iterator combo_mapping = group.comboboxes.find(type);
1845 if (combo_mapping != group.comboboxes.end())
1846 return combo_mapping->second;
1847 }
1848
1849 return NULL;
1850 }
1851
1852 ServerFieldType AutofillDialogViews::TypeForCombobox(
1853 const views::Combobox* combobox) const {
1854 for (DetailGroupMap::const_iterator it = detail_groups_.begin();
1855 it != detail_groups_.end(); ++it) {
1856 const DetailsGroup& group = it->second;
1857 if (!delegate_->SectionIsActive(group.section))
1858 continue;
1859
1860 for (ComboboxMap::const_iterator combo_it = group.comboboxes.begin();
1861 combo_it != group.comboboxes.end(); ++combo_it) {
1862 if (combo_it->second == combobox)
1863 return combo_it->first;
1864 }
1865 }
1866
1867 return UNKNOWN_TYPE;
1868 }
1869
1870 void AutofillDialogViews::DetailsContainerBoundsChanged() {
1871 if (error_bubble_)
1872 error_bubble_->UpdatePosition();
1873 }
1874
1875 void AutofillDialogViews::SetIconsForSection(DialogSection section) {
1876 FieldValueMap user_input;
1877 GetUserInput(section, &user_input);
1878 FieldIconMap field_icons = delegate_->IconsForFields(user_input);
1879 TextfieldMap* textfields = &GroupForSection(section)->textfields;
1880 for (TextfieldMap::const_iterator textfield_it = textfields->begin();
1881 textfield_it != textfields->end();
1882 ++textfield_it) {
1883 ServerFieldType field_type = textfield_it->first;
1884 FieldIconMap::const_iterator field_icon_it = field_icons.find(field_type);
1885 ExpandingTextfield* textfield = textfield_it->second;
1886 if (field_icon_it != field_icons.end())
1887 textfield->SetIcon(field_icon_it->second);
1888 else
1889 textfield->SetTooltipIcon(delegate_->TooltipForField(field_type));
1890 }
1891 }
1892
1893 void AutofillDialogViews::NonClientMousePressed() {
1894 delegate_->FocusMoved();
1895 }
1896
1897 views::View* AutofillDialogViews::GetNotificationAreaForTesting() {
1898 return notification_area_;
1899 }
1900
1901 views::View* AutofillDialogViews::GetScrollableAreaForTesting() {
1902 return scrollable_area_;
1903 }
1904
1905 AutofillDialogViews::DetailsGroup::DetailsGroup(DialogSection section)
1906 : section(section),
1907 container(NULL),
1908 manual_input(NULL),
1909 suggested_info(NULL),
1910 suggested_button(NULL) {}
1911
1912 AutofillDialogViews::DetailsGroup::DetailsGroup(const DetailsGroup& other) =
1913 default;
1914
1915 AutofillDialogViews::DetailsGroup::~DetailsGroup() {}
1916
1917 } // namespace autofill
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698