1 // Copyright 2013 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.
5 #include "chrome/browser/ui/views/passwords/manage_password_items_view.h"
9 #include "base/strings/utf_string_conversions.h"
10 #include "chrome/browser/ui/passwords/manage_passwords_bubble_model.h"
11 #include "chrome/grit/generated_resources.h"
12 #include "grit/components_strings.h"
13 #include "ui/base/l10n/l10n_util.h"
14 #include "ui/base/resource/resource_bundle.h"
15 #include "ui/resources/grit/ui_resources.h"
16 #include "ui/views/controls/button/button.h"
17 #include "ui/views/controls/button/image_button.h"
18 #include "ui/views/controls/label.h"
19 #include "ui/views/controls/link.h"
20 #include "ui/views/controls/link_listener.h"
21 #include "ui/views/layout/fill_layout.h"
22 #include "ui/views/layout/grid_layout.h"
23 #include "ui/views/layout/layout_constants.h"
33 void BuildColumnSetIfNeeded(views::GridLayout
* layout
, int column_set_id
) {
34 if (layout
->GetColumnSet(column_set_id
))
36 views::ColumnSet
* column_set
= layout
->AddColumnSet(column_set_id
);
38 // The username/"Deleted!"/Border field.
39 column_set
->AddPaddingColumn(0, views::kItemLabelSpacing
);
40 column_set
->AddColumn(views::GridLayout::FILL
,
41 views::GridLayout::FILL
,
43 views::GridLayout::USE_PREF
,
46 if (column_set_id
>= TWO_COLUMN_SET
) {
47 // The password/"Undo!" field.
48 column_set
->AddPaddingColumn(0, views::kItemLabelSpacing
);
49 column_set
->AddColumn(views::GridLayout::FILL
,
50 views::GridLayout::FILL
,
52 views::GridLayout::USE_PREF
,
56 // If we're in manage-mode, we need another column for the delete button.
57 if (column_set_id
== THREE_COLUMN_SET
) {
58 column_set
->AddPaddingColumn(0, views::kItemLabelSpacing
);
59 column_set
->AddColumn(views::GridLayout::TRAILING
,
60 views::GridLayout::FILL
,
62 views::GridLayout::USE_PREF
,
66 column_set
->AddPaddingColumn(0, views::kItemLabelSpacing
);
69 scoped_ptr
<views::Label
> GenerateUsernameLabel(
70 const autofill::PasswordForm
& form
) {
71 scoped_ptr
<views::Label
> label(new views::Label(
72 form
.username_value
.empty()
73 ? l10n_util::GetStringUTF16(IDS_PASSWORD_MANAGER_EMPTY_LOGIN
)
74 : form
.username_value
));
75 label
->SetFontList(ui::ResourceBundle::GetSharedInstance().GetFontList(
76 ui::ResourceBundle::SmallFont
));
77 label
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
81 scoped_ptr
<views::Label
> GeneratePasswordLabel(
82 const autofill::PasswordForm
& form
) {
83 base::string16 text
= form
.federation_url
.is_empty()
85 : l10n_util::GetStringFUTF16(
86 IDS_PASSWORDS_VIA_FEDERATION
,
87 base::UTF8ToUTF16(form
.federation_url
.host()));
88 scoped_ptr
<views::Label
> label(new views::Label(text
));
89 label
->SetFontList(ui::ResourceBundle::GetSharedInstance().GetFontList(
90 ui::ResourceBundle::SmallFont
));
91 label
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
92 if (form
.federation_url
.is_empty())
93 label
->SetObscured(true);
97 scoped_ptr
<views::ImageButton
> GenerateDeleteButton(
98 views::ButtonListener
* listener
) {
99 ui::ResourceBundle
* rb
= &ui::ResourceBundle::GetSharedInstance();
100 scoped_ptr
<views::ImageButton
> button(new views::ImageButton(listener
));
101 button
->SetImage(views::ImageButton::STATE_NORMAL
,
102 rb
->GetImageNamed(IDR_CLOSE_2
).ToImageSkia());
103 button
->SetImage(views::ImageButton::STATE_HOVERED
,
104 rb
->GetImageNamed(IDR_CLOSE_2_H
).ToImageSkia());
105 button
->SetImage(views::ImageButton::STATE_PRESSED
,
106 rb
->GetImageNamed(IDR_CLOSE_2_P
).ToImageSkia());
107 return button
.Pass();
110 scoped_ptr
<views::Label
> GenerateDeletedPasswordLabel() {
111 scoped_ptr
<views::Label
> text(new views::Label(
112 l10n_util::GetStringUTF16(IDS_MANAGE_PASSWORDS_DELETED
)));
113 text
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
114 text
->SetFontList(ui::ResourceBundle::GetSharedInstance().GetFontList(
115 ui::ResourceBundle::SmallFont
));
119 scoped_ptr
<views::Link
> GenerateUndoLink(views::LinkListener
* listener
) {
120 scoped_ptr
<views::Link
> undo_link(new views::Link(
121 l10n_util::GetStringUTF16(IDS_MANAGE_PASSWORDS_UNDO
)));
122 undo_link
->SetHorizontalAlignment(gfx::ALIGN_RIGHT
);
123 undo_link
->set_listener(listener
);
124 undo_link
->SetUnderline(false);
125 undo_link
->SetFontList(ui::ResourceBundle::GetSharedInstance().GetFontList(
126 ui::ResourceBundle::SmallFont
));
127 return undo_link
.Pass();
132 // Manage credentials: stores credentials state and adds proper row to layout
133 // based on credential state.
134 class ManagePasswordItemsView::PasswordFormRow
: public views::ButtonListener
,
135 public views::LinkListener
{
137 PasswordFormRow(ManagePasswordItemsView
* host
,
138 const autofill::PasswordForm
* password_form
,
140 ~PasswordFormRow() override
= default;
142 void AddRow(views::GridLayout
* layout
);
144 // Returns the fixed height for a row excluding padding. 0 means no fixed
146 // In MANAGE_STATE a row may represent a credential or a deleted credential.
147 // To avoid repositioning all the rows should have a fixed height.
148 static int GetFixedHeight(password_manager::ui::State state
);
151 void AddCredentialsRow(views::GridLayout
* layout
);
152 void AddUndoRow(views::GridLayout
* layout
);
154 // views::ButtonListener:
155 void ButtonPressed(views::Button
* sender
, const ui::Event
& event
) override
;
156 // views::LinkListener:
157 void LinkClicked(views::Link
* source
, int event_flags
) override
;
159 void ResetControls();
161 ManagePasswordItemsView
* host_
;
162 const autofill::PasswordForm
* password_form_
;
163 // The UI elements pointers are weak and owned by their parent.
164 views::Link
* undo_link_
;
165 views::ImageButton
* delete_button_
;
166 const int fixed_height_
;
169 DISALLOW_COPY_AND_ASSIGN(PasswordFormRow
);
172 ManagePasswordItemsView::PasswordFormRow::PasswordFormRow(
173 ManagePasswordItemsView
* host
,
174 const autofill::PasswordForm
* password_form
,
177 password_form_(password_form
),
179 delete_button_(nullptr),
180 fixed_height_(fixed_height
),
183 void ManagePasswordItemsView::PasswordFormRow::AddRow(
184 views::GridLayout
* layout
) {
188 AddCredentialsRow(layout
);
192 int ManagePasswordItemsView::PasswordFormRow::GetFixedHeight(
193 password_manager::ui::State state
) {
194 if (state
!= password_manager::ui::MANAGE_STATE
)
196 scoped_ptr
<views::ImageButton
> delete_button(GenerateDeleteButton(nullptr));
197 scoped_ptr
<views::Link
> link(GenerateUndoLink(nullptr));
198 scoped_ptr
<views::Label
> label(GenerateDeletedPasswordLabel());
199 views::View
* row_views
[] = {delete_button
.get(), link
.get(), label
.get()};
200 return std::accumulate(row_views
, row_views
+ arraysize(row_views
), 0,
201 [](int max_height
, const views::View
* view
) {
202 return std::max(max_height
, view
->GetPreferredSize().height());
206 void ManagePasswordItemsView::PasswordFormRow::AddCredentialsRow(
207 views::GridLayout
* layout
) {
210 host_
->model_
->state() == password_manager::ui::MANAGE_STATE
213 BuildColumnSetIfNeeded(layout
, column_set_id
);
214 layout
->StartRowWithPadding(0, column_set_id
, 0,
215 views::kRelatedControlVerticalSpacing
);
216 layout
->AddView(GenerateUsernameLabel(*password_form_
).release(), 1, 1,
217 views::GridLayout::FILL
, views::GridLayout::FILL
,
219 layout
->AddView(GeneratePasswordLabel(*password_form_
).release(), 1, 1,
220 views::GridLayout::FILL
, views::GridLayout::FILL
,
222 if (column_set_id
== THREE_COLUMN_SET
) {
223 delete_button_
= GenerateDeleteButton(this).release();
224 layout
->AddView(delete_button_
, 1, 1,
225 views::GridLayout::TRAILING
, views::GridLayout::FILL
,
230 void ManagePasswordItemsView::PasswordFormRow::AddUndoRow(
231 views::GridLayout
* layout
) {
233 scoped_ptr
<views::Label
> text
= GenerateDeletedPasswordLabel();
234 scoped_ptr
<views::Link
> undo_link
= GenerateUndoLink(this);
235 undo_link_
= undo_link
.get();
236 BuildColumnSetIfNeeded(layout
, TWO_COLUMN_SET
);
237 layout
->StartRowWithPadding(0, TWO_COLUMN_SET
, 0,
238 views::kRelatedControlVerticalSpacing
);
239 layout
->AddView(text
.release(), 1, 1,
240 views::GridLayout::FILL
, views::GridLayout::FILL
,
242 layout
->AddView(undo_link
.release(), 1, 1,
243 views::GridLayout::FILL
, views::GridLayout::FILL
,
247 void ManagePasswordItemsView::PasswordFormRow::ButtonPressed(
248 views::Button
* sender
, const ui::Event
& event
) {
249 DCHECK_EQ(delete_button_
, sender
);
251 host_
->NotifyPasswordFormStatusChanged(*password_form_
, deleted_
);
254 void ManagePasswordItemsView::PasswordFormRow::LinkClicked(views::Link
* sender
,
256 DCHECK_EQ(undo_link_
, sender
);
258 host_
->NotifyPasswordFormStatusChanged(*password_form_
, deleted_
);
261 void ManagePasswordItemsView::PasswordFormRow::ResetControls() {
262 delete_button_
= nullptr;
263 undo_link_
= nullptr;
266 // ManagePasswordItemsView
267 ManagePasswordItemsView::ManagePasswordItemsView(
268 ManagePasswordsBubbleModel
* manage_passwords_bubble_model
,
269 const std::vector
<const autofill::PasswordForm
*>& password_forms
)
270 : model_(manage_passwords_bubble_model
) {
271 int fixed_height
= PasswordFormRow::GetFixedHeight(model_
->state());
272 for (const autofill::PasswordForm
* password_form
: password_forms
) {
273 password_forms_rows_
.push_back(
274 new PasswordFormRow(this, password_form
, fixed_height
));
279 ManagePasswordItemsView::~ManagePasswordItemsView() = default;
281 void ManagePasswordItemsView::AddRows() {
282 views::GridLayout
* layout
= new views::GridLayout(this);
283 SetLayoutManager(layout
);
284 for (auto* row
: password_forms_rows_
) {
287 GetLayoutManager()->Layout(this);
290 void ManagePasswordItemsView::NotifyPasswordFormStatusChanged(
291 const autofill::PasswordForm
& password_form
, bool deleted
) {
293 // After the view is consistent, notify the model that the password needs to
294 // be updated (either removed or put back into the store, as appropriate.
295 model_
->OnPasswordAction(password_form
,
297 ? ManagePasswordsBubbleModel::REMOVE_PASSWORD
298 : ManagePasswordsBubbleModel::ADD_PASSWORD
);
301 void ManagePasswordItemsView::Refresh() {
302 DCHECK_NE(password_manager::ui::PENDING_PASSWORD_STATE
, model_
->state());
303 RemoveAllChildViews(true);