OLD | NEW |
| (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 | |
OLD | NEW |