1// Copyright 2014 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/password_generation_popup_view_views.h"
6
7#include "base/strings/string16.h"
8#include "chrome/browser/ui/autofill/password_generation_popup_controller.h"
9#include "chrome/browser/ui/autofill/popup_constants.h"
10#include "grit/theme_resources.h"
11#include "ui/base/resource/resource_bundle.h"
12#include "ui/gfx/canvas.h"
13#include "ui/views/background.h"
14#include "ui/views/border.h"
15#include "ui/views/controls/image_view.h"
16#include "ui/views/controls/label.h"
17#include "ui/views/controls/styled_label.h"
18#include "ui/views/layout/box_layout.h"
19#include "ui/views/widget/widget.h"
20
21namespace autofill {
22
23namespace {
24
25// The amount of whitespace that is present when there is no padding. Used
26// to get the proper spacing in the help section.
27const int kHelpVerticalOffset = 5;
28
29// Wrapper around just the text portions of the generation UI (password and
30// prompting text).
31class PasswordTextBox : public views::View {
32 public:
33  PasswordTextBox() {}
34  virtual ~PasswordTextBox() {}
35
36  // |suggestion_text| prompts the user to select the password,
37  // |generated_password| is the generated password, and |font_list| is the font
38  // used for all text in this class.
39  void Init(const base::string16& suggestion_text,
40            const base::string16& generated_password,
41            const gfx::FontList& font_list) {
42    views::BoxLayout* box_layout = new views::BoxLayout(
43        views::BoxLayout::kVertical, 0, 12, 5);
44    box_layout->set_main_axis_alignment(
45        views::BoxLayout::MAIN_AXIS_ALIGNMENT_START);
46    SetLayoutManager(box_layout);
47
48    views::Label* suggestion_label = new views::Label(
49        suggestion_text, font_list.DeriveWithStyle(gfx::Font::BOLD));
50    suggestion_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
51    suggestion_label->SetEnabledColor(
52        PasswordGenerationPopupView::kPasswordTextColor);
53    AddChildView(suggestion_label);
54
55    views::Label* password_label =
56        new views::Label(generated_password, font_list);
57    password_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
58    password_label->SetEnabledColor(
59        PasswordGenerationPopupView::kPasswordTextColor);
60    AddChildView(password_label);
61  }
62
63  // views::View:
64  virtual bool CanProcessEventsWithinSubtree() const OVERRIDE {
65    // Send events to the parent view for handling.
66    return false;
67  }
68
69 private:
70  DISALLOW_COPY_AND_ASSIGN(PasswordTextBox);
71};
72
73}  // namespace
74
75// Class that shows the generated password and associated UI (currently a key
76// image and some explanatory text).
77class PasswordGenerationPopupViewViews::PasswordBox : public views::View {
78 public:
79  PasswordBox() {}
80  virtual ~PasswordBox() {}
81
82  // |password| is the generated password, |suggestion| is the text prompting
83  // the user to select the password, and |font_list| is the font used for all
84  // the text.
85  void Init(const base::string16& password,
86            const base::string16& suggestion,
87            const gfx::FontList& font_list) {
88    views::BoxLayout* box_layout = new views::BoxLayout(
89        views::BoxLayout::kHorizontal,
90        PasswordGenerationPopupController::kHorizontalPadding,
91        0,
92        15);
93    box_layout->set_main_axis_alignment(
94        views::BoxLayout::MAIN_AXIS_ALIGNMENT_START);
95    SetLayoutManager(box_layout);
96
97    views::ImageView* key_image = new views::ImageView();
98    key_image->SetImage(
99        ui::ResourceBundle::GetSharedInstance().GetImageNamed(
100            IDR_GENERATE_PASSWORD_KEY).ToImageSkia());
101    AddChildView(key_image);
102
103    PasswordTextBox* password_text_box = new PasswordTextBox();
104    password_text_box->Init(suggestion, password, font_list);
105    AddChildView(password_text_box);
106  }
107
108  // views::View:
109  virtual bool CanProcessEventsWithinSubtree() const OVERRIDE {
110    // Send events to the parent view for handling.
111    return false;
112  }
113
114 private:
115  DISALLOW_COPY_AND_ASSIGN(PasswordBox);
116};
117
118PasswordGenerationPopupViewViews::PasswordGenerationPopupViewViews(
119    PasswordGenerationPopupController* controller,
120    views::Widget* observing_widget)
121    : AutofillPopupBaseView(controller, observing_widget),
122      password_view_(NULL),
123      font_list_(ResourceBundle::GetSharedInstance().GetFontList(
124          ResourceBundle::SmallFont)),
125      controller_(controller) {
126  if (controller_->display_password())
127    CreatePasswordView();
128
129  help_label_ = new views::StyledLabel(controller_->HelpText(), this);
130  help_label_->SetBaseFontList(font_list_);
131  help_label_->SetLineHeight(20);
132  views::StyledLabel::RangeStyleInfo default_style;
133  default_style.color = kExplanatoryTextColor;
134  help_label_->SetDefaultStyle(default_style);
135
136  views::StyledLabel::RangeStyleInfo link_style =
137      views::StyledLabel::RangeStyleInfo::CreateForLink();
138  link_style.disable_line_wrapping = false;
139  help_label_->AddStyleRange(controller_->HelpTextLinkRange(), link_style);
140
141  help_label_->set_background(
142      views::Background::CreateSolidBackground(
143          kExplanatoryTextBackgroundColor));
144  help_label_->SetBorder(views::Border::CreateEmptyBorder(
145      PasswordGenerationPopupController::kHelpVerticalPadding -
146      kHelpVerticalOffset,
147      PasswordGenerationPopupController::kHorizontalPadding,
148      PasswordGenerationPopupController::kHelpVerticalPadding -
149      kHelpVerticalOffset,
150      PasswordGenerationPopupController::kHorizontalPadding));
151  AddChildView(help_label_);
152
153  set_background(views::Background::CreateSolidBackground(kPopupBackground));
154}
155
156PasswordGenerationPopupViewViews::~PasswordGenerationPopupViewViews() {}
157
158void PasswordGenerationPopupViewViews::CreatePasswordView() {
159  if (password_view_)
160    return;
161
162  password_view_ = new PasswordBox();
163  password_view_->Init(controller_->password(),
164                       controller_->SuggestedText(),
165                       font_list_);
166  password_view_->SetPosition(gfx::Point(kPopupBorderThickness,
167                                         kPopupBorderThickness));
168  password_view_->SizeToPreferredSize();
169  AddChildView(password_view_);
170}
171
172gfx::Size PasswordGenerationPopupViewViews::GetPreferredSizeOfPasswordView() {
173  int height = kPopupBorderThickness;
174  if (controller_->display_password()) {
175    // Add divider height as well.
176    height +=
177        PasswordGenerationPopupController::kPopupPasswordSectionHeight + 1;
178  }
179  int width = controller_->GetMinimumWidth();
180  int popup_width = width - 2 * kPopupBorderThickness;
181  height += help_label_->GetHeightForWidth(popup_width);
182  return gfx::Size(width, height + kPopupBorderThickness);
183}
184
185void PasswordGenerationPopupViewViews::Show() {
186  DoShow();
187}
188
189void PasswordGenerationPopupViewViews::Hide() {
190  // The controller is no longer valid after it hides us.
191  controller_ = NULL;
192
193  DoHide();
194}
195
196void PasswordGenerationPopupViewViews::UpdateBoundsAndRedrawPopup() {
197  DoUpdateBoundsAndRedrawPopup();
198}
199
200void PasswordGenerationPopupViewViews::PasswordSelectionUpdated() {
201  if (!password_view_)
202    return;
203
204  password_view_->set_background(
205      views::Background::CreateSolidBackground(
206          controller_->password_selected() ?
207          kHoveredBackgroundColor :
208          kPopupBackground));
209}
210
211void PasswordGenerationPopupViewViews::Layout() {
212  // Need to leave room for the border.
213  int y = kPopupBorderThickness;
214  int popup_width = bounds().width() - 2 * kPopupBorderThickness;
215  if (controller_->display_password()) {
216    // Currently the UI can change from not offering a password to offering
217    // a password (e.g. the user is editing a generated password and deletes
218    // it), but it can't change the other way around.
219    CreatePasswordView();
220    password_view_->SetBounds(
221        kPopupBorderThickness,
222        y,
223        popup_width,
224        PasswordGenerationPopupController::kPopupPasswordSectionHeight);
225    divider_bounds_ =
226        gfx::Rect(kPopupBorderThickness, password_view_->bounds().bottom(),
227                  popup_width, 1);
228    y = divider_bounds_.bottom();
229  }
230
231  help_label_->SetBounds(kPopupBorderThickness, y, popup_width,
232                         help_label_->GetHeightForWidth(popup_width));
233}
234
235void PasswordGenerationPopupViewViews::OnPaint(gfx::Canvas* canvas) {
236  if (!controller_)
237    return;
238
239  // Draw border and background.
240  views::View::OnPaint(canvas);
241
242  // Divider line needs to be drawn after OnPaint() otherwise the background
243  // will overwrite the divider.
244  if (password_view_)
245    canvas->FillRect(divider_bounds_, kDividerColor);
246}
247
248void PasswordGenerationPopupViewViews::StyledLabelLinkClicked(
249    const gfx::Range& range, int event_flags) {
250  controller_->OnSavedPasswordsLinkClicked();
251}
252
253bool PasswordGenerationPopupViewViews::IsPointInPasswordBounds(
254    const gfx::Point& point) {
255  if (password_view_)
256    return password_view_->bounds().Contains(point);
257  return false;
258}
259
260PasswordGenerationPopupView* PasswordGenerationPopupView::Create(
261    PasswordGenerationPopupController* controller) {
262  views::Widget* observing_widget =
263      views::Widget::GetTopLevelWidgetForNativeView(
264          controller->container_view());
265
266  // If the top level widget can't be found, cancel the popup since we can't
267  // fully set it up.
268  if (!observing_widget)
269    return NULL;
270
271  return new PasswordGenerationPopupViewViews(controller, observing_widget);
272}
273
274}  // namespace autofill
275