| 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 |