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_passwords_bubble_view.h"
7 #include "chrome/browser/chrome_notification_types.h"
8 #include "chrome/browser/ui/browser.h"
9 #include "chrome/browser/ui/browser_finder.h"
10 #include "chrome/browser/ui/browser_window.h"
11 #include "chrome/browser/ui/passwords/manage_passwords_bubble_model.h"
12 #include "chrome/browser/ui/passwords/manage_passwords_ui_controller.h"
13 #include "chrome/browser/ui/views/frame/browser_view.h"
14 #include "chrome/browser/ui/views/location_bar/location_bar_view.h"
15 #include "chrome/browser/ui/views/passwords/manage_password_item_view.h"
16 #include "chrome/browser/ui/views/passwords/manage_passwords_icon_view.h"
17 #include "components/password_manager/core/common/password_manager_ui.h"
18 #include "content/public/browser/notification_source.h"
19 #include "grit/generated_resources.h"
20 #include "ui/base/l10n/l10n_util.h"
21 #include "ui/base/models/combobox_model.h"
22 #include "ui/base/resource/resource_bundle.h"
23 #include "ui/gfx/text_utils.h"
24 #include "ui/views/controls/button/blue_button.h"
25 #include "ui/views/controls/button/label_button.h"
26 #include "ui/views/controls/combobox/combobox.h"
27 #include "ui/views/layout/fill_layout.h"
28 #include "ui/views/layout/grid_layout.h"
29 #include "ui/views/layout/layout_constants.h"
32 // Helpers --------------------------------------------------------------------
36 const int kDesiredBubbleWidth
= 370;
39 // | | (FILL, FILL) | |
40 // Used for the bubble's header, the credentials list, and for simple
41 // messages like "No passwords".
42 SINGLE_VIEW_COLUMN_SET
= 0,
44 // | | (TRAILING, CENTER) | | (TRAILING, CENTER) | |
45 // Used for buttons at the bottom of the bubble which should nest at the
46 // bottom-right corner.
47 DOUBLE_BUTTON_COLUMN_SET
= 1,
49 // | | (LEADING, CENTER) | | (TRAILING, CENTER) | |
50 // Used for buttons at the bottom of the bubble which should occupy
52 LINK_BUTTON_COLUMN_SET
= 2,
55 // Construct an appropriate ColumnSet for the given |type|, and add it
57 void BuildColumnSet(views::GridLayout
* layout
, ColumnSetType type
) {
58 views::ColumnSet
* column_set
= layout
->AddColumnSet(type
);
59 column_set
->AddPaddingColumn(0, views::kPanelHorizMargin
);
61 case SINGLE_VIEW_COLUMN_SET
:
62 column_set
->AddColumn(views::GridLayout::FILL
,
63 views::GridLayout::FILL
,
65 views::GridLayout::USE_PREF
,
70 case DOUBLE_BUTTON_COLUMN_SET
:
71 column_set
->AddColumn(views::GridLayout::TRAILING
,
72 views::GridLayout::CENTER
,
74 views::GridLayout::USE_PREF
,
77 column_set
->AddPaddingColumn(0, views::kRelatedButtonHSpacing
);
78 column_set
->AddColumn(views::GridLayout::TRAILING
,
79 views::GridLayout::CENTER
,
81 views::GridLayout::USE_PREF
,
85 case LINK_BUTTON_COLUMN_SET
:
86 column_set
->AddColumn(views::GridLayout::LEADING
,
87 views::GridLayout::CENTER
,
89 views::GridLayout::USE_PREF
,
92 column_set
->AddPaddingColumn(0, views::kRelatedButtonHSpacing
);
93 column_set
->AddColumn(views::GridLayout::TRAILING
,
94 views::GridLayout::CENTER
,
96 views::GridLayout::USE_PREF
,
101 column_set
->AddPaddingColumn(0, views::kPanelHorizMargin
);
104 // Given a layout and a model, add an appropriate title using a
105 // SINGLE_VIEW_COLUMN_SET, followed by a spacer row.
106 void AddTitleRow(views::GridLayout
* layout
, ManagePasswordsBubbleModel
* model
) {
107 views::Label
* title_label
= new views::Label(model
->title());
108 title_label
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
109 title_label
->SetMultiLine(true);
110 title_label
->SetFontList(ui::ResourceBundle::GetSharedInstance().GetFontList(
111 ui::ResourceBundle::MediumFont
));
113 // Add the title to the layout with appropriate padding.
114 layout
->StartRowWithPadding(
115 0, SINGLE_VIEW_COLUMN_SET
, 0, views::kRelatedControlSmallVerticalSpacing
);
116 layout
->AddView(title_label
);
117 layout
->AddPaddingRow(0, views::kUnrelatedControlVerticalSpacing
);
123 // Globals --------------------------------------------------------------------
127 void ShowManagePasswordsBubble(content::WebContents
* web_contents
) {
128 ManagePasswordsUIController
* controller
=
129 ManagePasswordsUIController::FromWebContents(web_contents
);
130 ManagePasswordsBubbleView::ShowBubble(
132 controller
->state() ==
133 password_manager::ui::PENDING_PASSWORD_AND_BUBBLE_STATE
134 ? ManagePasswordsBubbleView::AUTOMATIC
135 : ManagePasswordsBubbleView::USER_ACTION
);
138 } // namespace chrome
141 // ManagePasswordsBubbleView::PendingView -------------------------------------
143 ManagePasswordsBubbleView::PendingView::PendingView(
144 ManagePasswordsBubbleView
* parent
)
146 views::GridLayout
* layout
= new views::GridLayout(this);
147 layout
->set_minimum_size(gfx::Size(kDesiredBubbleWidth
, 0));
148 SetLayoutManager(layout
);
150 // Create the pending credential item, save button and refusal combobox.
151 ManagePasswordItemView
* item
=
152 new ManagePasswordItemView(parent
->model(),
153 parent
->model()->pending_credentials(),
154 ManagePasswordItemView::FIRST_ITEM
);
155 save_button_
= new views::BlueButton(
156 this, l10n_util::GetStringUTF16(IDS_PASSWORD_MANAGER_SAVE_BUTTON
));
157 save_button_
->SetFontList(ui::ResourceBundle::GetSharedInstance().GetFontList(
158 ui::ResourceBundle::SmallFont
));
160 combobox_model_
.reset(new SavePasswordRefusalComboboxModel());
161 refuse_combobox_
.reset(new views::Combobox(combobox_model_
.get()));
162 refuse_combobox_
->set_listener(this);
163 refuse_combobox_
->SetStyle(views::Combobox::STYLE_ACTION
);
164 // TODO(mkwst): Need a mechanism to pipe a font list down into a combobox.
167 BuildColumnSet(layout
, SINGLE_VIEW_COLUMN_SET
);
168 AddTitleRow(layout
, parent_
->model());
171 layout
->StartRow(0, SINGLE_VIEW_COLUMN_SET
);
172 layout
->AddView(item
);
175 BuildColumnSet(layout
, DOUBLE_BUTTON_COLUMN_SET
);
176 layout
->StartRowWithPadding(
177 0, DOUBLE_BUTTON_COLUMN_SET
, 0, views::kRelatedControlVerticalSpacing
);
178 layout
->AddView(save_button_
);
179 layout
->AddView(refuse_combobox_
.get());
181 // Extra padding for visual awesomeness.
182 layout
->AddPaddingRow(0, views::kRelatedControlVerticalSpacing
);
185 ManagePasswordsBubbleView::PendingView::~PendingView() {
188 void ManagePasswordsBubbleView::PendingView::ButtonPressed(
189 views::Button
* sender
,
190 const ui::Event
& event
) {
191 DCHECK(sender
== save_button_
);
192 parent_
->model()->OnSaveClicked();
196 void ManagePasswordsBubbleView::PendingView::OnPerformAction(
197 views::Combobox
* source
) {
198 DCHECK_EQ(source
, refuse_combobox_
);
199 switch (refuse_combobox_
->selected_index()) {
200 case SavePasswordRefusalComboboxModel::INDEX_NOPE
:
201 parent_
->model()->OnNopeClicked();
203 case SavePasswordRefusalComboboxModel::INDEX_NEVER_FOR_THIS_SITE
:
204 parent_
->model()->OnNeverForThisSiteClicked();
210 // ManagePasswordsBubbleView::ManageView --------------------------------------
212 ManagePasswordsBubbleView::ManageView::ManageView(
213 ManagePasswordsBubbleView
* parent
)
215 views::GridLayout
* layout
= new views::GridLayout(this);
216 layout
->set_minimum_size(gfx::Size(kDesiredBubbleWidth
, 0));
217 SetLayoutManager(layout
);
220 BuildColumnSet(layout
, SINGLE_VIEW_COLUMN_SET
);
221 AddTitleRow(layout
, parent_
->model());
223 // If we have a list of passwords to store for the current site, display
224 // them to the user for management. Otherwise, render a "No passwords for
225 // this site" message.
226 if (!parent_
->model()->best_matches().empty()) {
227 for (autofill::PasswordFormMap::const_iterator
i(
228 parent_
->model()->best_matches().begin());
229 i
!= parent_
->model()->best_matches().end();
231 ManagePasswordItemView
* item
= new ManagePasswordItemView(
234 i
== parent_
->model()->best_matches().begin()
235 ? ManagePasswordItemView::FIRST_ITEM
236 : ManagePasswordItemView::SUBSEQUENT_ITEM
);
238 layout
->StartRow(0, SINGLE_VIEW_COLUMN_SET
);
239 layout
->AddView(item
);
242 views::Label
* empty_label
= new views::Label(
243 l10n_util::GetStringUTF16(IDS_MANAGE_PASSWORDS_NO_PASSWORDS
));
244 empty_label
->SetMultiLine(true);
245 empty_label
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
246 empty_label
->SetFontList(
247 ui::ResourceBundle::GetSharedInstance().GetFontList(
248 ui::ResourceBundle::SmallFont
));
250 layout
->StartRow(0, SINGLE_VIEW_COLUMN_SET
);
251 layout
->AddView(empty_label
);
252 layout
->AddPaddingRow(0, views::kRelatedControlSmallVerticalSpacing
);
255 // Then add the "manage passwords" link and "Done" button.
256 manage_link_
= new views::Link(parent_
->model()->manage_link());
257 manage_link_
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
258 manage_link_
->SetFontList(ui::ResourceBundle::GetSharedInstance().GetFontList(
259 ui::ResourceBundle::SmallFont
));
260 manage_link_
->SetUnderline(false);
261 manage_link_
->set_listener(this);
264 new views::LabelButton(this, l10n_util::GetStringUTF16(IDS_DONE
));
265 done_button_
->SetStyle(views::Button::STYLE_BUTTON
);
266 done_button_
->SetFontList(ui::ResourceBundle::GetSharedInstance().GetFontList(
267 ui::ResourceBundle::SmallFont
));
269 BuildColumnSet(layout
, LINK_BUTTON_COLUMN_SET
);
270 layout
->StartRowWithPadding(
271 0, LINK_BUTTON_COLUMN_SET
, 0, views::kRelatedControlVerticalSpacing
);
272 layout
->AddView(manage_link_
);
273 layout
->AddView(done_button_
);
275 // Extra padding for visual awesomeness.
276 layout
->AddPaddingRow(0, views::kRelatedControlVerticalSpacing
);
279 ManagePasswordsBubbleView::ManageView::~ManageView() {
282 void ManagePasswordsBubbleView::ManageView::ButtonPressed(
283 views::Button
* sender
,
284 const ui::Event
& event
) {
285 DCHECK(sender
== done_button_
);
286 parent_
->model()->OnDoneClicked();
290 void ManagePasswordsBubbleView::ManageView::LinkClicked(views::Link
* source
,
292 DCHECK_EQ(source
, manage_link_
);
293 parent_
->model()->OnManageLinkClicked();
297 // ManagePasswordsBubbleView::BlacklistedView ---------------------------------
299 ManagePasswordsBubbleView::BlacklistedView::BlacklistedView(
300 ManagePasswordsBubbleView
* parent
)
302 views::GridLayout
* layout
= new views::GridLayout(this);
303 layout
->set_minimum_size(gfx::Size(kDesiredBubbleWidth
, 0));
304 SetLayoutManager(layout
);
307 BuildColumnSet(layout
, SINGLE_VIEW_COLUMN_SET
);
308 AddTitleRow(layout
, parent_
->model());
310 // Add the "Hey! You blacklisted this site!" text.
311 views::Label
* blacklisted
= new views::Label(
312 l10n_util::GetStringUTF16(IDS_MANAGE_PASSWORDS_BLACKLISTED
));
313 blacklisted
->SetMultiLine(true);
314 blacklisted
->SetFontList(ui::ResourceBundle::GetSharedInstance().GetFontList(
315 ui::ResourceBundle::SmallFont
));
316 layout
->StartRow(0, SINGLE_VIEW_COLUMN_SET
);
317 layout
->AddView(blacklisted
);
318 layout
->AddPaddingRow(0, views::kRelatedControlSmallVerticalSpacing
);
320 // Then add the "enable password manager" and "Done" buttons.
321 unblacklist_button_
= new views::BlueButton(
322 this, l10n_util::GetStringUTF16(IDS_PASSWORD_MANAGER_UNBLACKLIST_BUTTON
));
323 unblacklist_button_
->SetFontList(
324 ui::ResourceBundle::GetSharedInstance().GetFontList(
325 ui::ResourceBundle::SmallFont
));
327 new views::LabelButton(this, l10n_util::GetStringUTF16(IDS_DONE
));
328 done_button_
->SetStyle(views::Button::STYLE_BUTTON
);
329 done_button_
->SetFontList(ui::ResourceBundle::GetSharedInstance().GetFontList(
330 ui::ResourceBundle::SmallFont
));
332 BuildColumnSet(layout
, DOUBLE_BUTTON_COLUMN_SET
);
333 layout
->StartRowWithPadding(
334 0, DOUBLE_BUTTON_COLUMN_SET
, 0, views::kRelatedControlVerticalSpacing
);
335 layout
->AddView(unblacklist_button_
);
336 layout
->AddView(done_button_
);
338 // Extra padding for visual awesomeness.
339 layout
->AddPaddingRow(0, views::kRelatedControlVerticalSpacing
);
342 ManagePasswordsBubbleView::BlacklistedView::~BlacklistedView() {
345 void ManagePasswordsBubbleView::BlacklistedView::ButtonPressed(
346 views::Button
* sender
,
347 const ui::Event
& event
) {
348 if (sender
== done_button_
)
349 parent_
->model()->OnDoneClicked();
350 else if (sender
== unblacklist_button_
)
351 parent_
->model()->OnUnblacklistClicked();
357 // ManagePasswordsBubbleView --------------------------------------------------
360 ManagePasswordsBubbleView
* ManagePasswordsBubbleView::manage_passwords_bubble_
=
364 void ManagePasswordsBubbleView::ShowBubble(content::WebContents
* web_contents
,
365 DisplayReason reason
) {
366 Browser
* browser
= chrome::FindBrowserWithWebContents(web_contents
);
368 DCHECK(browser
->window());
369 DCHECK(browser
->fullscreen_controller());
374 BrowserView
* browser_view
= BrowserView::GetBrowserViewForBrowser(browser
);
375 bool is_fullscreen
= browser_view
->IsFullscreen();
376 views::View
* anchor_view
= is_fullscreen
?
377 NULL
: browser_view
->GetLocationBarView()->manage_passwords_icon_view();
378 manage_passwords_bubble_
= new ManagePasswordsBubbleView(
379 web_contents
, anchor_view
, reason
);
382 manage_passwords_bubble_
->set_parent_window(
383 web_contents
->GetTopLevelNativeWindow());
386 views::BubbleDelegateView::CreateBubble(manage_passwords_bubble_
);
388 // Adjust for fullscreen after creation as it relies on the content size.
390 manage_passwords_bubble_
->AdjustForFullscreen(
391 browser_view
->GetBoundsInScreen());
394 manage_passwords_bubble_
->GetWidget()->Show();
398 void ManagePasswordsBubbleView::CloseBubble() {
399 if (manage_passwords_bubble_
)
400 manage_passwords_bubble_
->Close();
404 bool ManagePasswordsBubbleView::IsShowing() {
405 // The bubble may be in the process of closing.
406 return (manage_passwords_bubble_
!= NULL
) &&
407 manage_passwords_bubble_
->GetWidget()->IsVisible();
410 ManagePasswordsBubbleView::ManagePasswordsBubbleView(
411 content::WebContents
* web_contents
,
412 views::View
* anchor_view
,
413 DisplayReason reason
)
414 : ManagePasswordsBubble(web_contents
, reason
),
415 BubbleDelegateView(anchor_view
,
416 anchor_view
? views::BubbleBorder::TOP_RIGHT
417 : views::BubbleBorder::NONE
) {
418 // Compensate for built-in vertical padding in the anchor view's image.
419 set_anchor_view_insets(gfx::Insets(5, 0, 5, 0));
420 set_notify_enter_exit_on_child(true);
423 ManagePasswordsBubbleView::~ManagePasswordsBubbleView() {}
425 void ManagePasswordsBubbleView::AdjustForFullscreen(
426 const gfx::Rect
& screen_bounds
) {
430 // The bubble's padding from the screen edge, used in fullscreen.
431 const int kFullscreenPaddingEnd
= 20;
432 const size_t bubble_half_width
= width() / 2;
433 const int x_pos
= base::i18n::IsRTL() ?
434 screen_bounds
.x() + bubble_half_width
+ kFullscreenPaddingEnd
:
435 screen_bounds
.right() - bubble_half_width
- kFullscreenPaddingEnd
;
436 SetAnchorRect(gfx::Rect(x_pos
, screen_bounds
.y(), 0, 0));
439 void ManagePasswordsBubbleView::Close() {
440 GetWidget()->Close();
443 void ManagePasswordsBubbleView::Init() {
444 views::FillLayout
* layout
= new views::FillLayout();
445 SetLayoutManager(layout
);
448 if (password_manager::ui::IsPendingState(model()->state()))
449 AddChildView(new PendingView(this));
450 else if (model()->state() == password_manager::ui::BLACKLIST_STATE
)
451 AddChildView(new BlacklistedView(this));
453 AddChildView(new ManageView(this));
456 void ManagePasswordsBubbleView::WindowClosing() {
457 // Close() closes the window asynchronously, so by the time we reach here,
458 // |manage_passwords_bubble_| may have already been reset.
459 if (manage_passwords_bubble_
== this)
460 manage_passwords_bubble_
= NULL
;