manage_passwords_bubble_view.cc revision 46d4c2bc3267f3f028f39e7e311b0f89aba2e4fd
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.
4
5#include "chrome/browser/ui/views/passwords/manage_passwords_bubble_view.h"
6
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"
30
31
32// Helpers --------------------------------------------------------------------
33
34namespace {
35
36const int kDesiredBubbleWidth = 370;
37
38enum ColumnSetType {
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,
43
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,
48
49  // | | (LEADING, CENTER) | | (TRAILING, CENTER) | |
50  // Used for buttons at the bottom of the bubble which should occupy
51  // the corners.
52  LINK_BUTTON_COLUMN_SET = 2,
53};
54
55// Construct an appropriate ColumnSet for the given |type|, and add it
56// to |layout|.
57void BuildColumnSet(views::GridLayout* layout, ColumnSetType type) {
58  views::ColumnSet* column_set = layout->AddColumnSet(type);
59  column_set->AddPaddingColumn(0, views::kPanelHorizMargin);
60  switch (type) {
61    case SINGLE_VIEW_COLUMN_SET:
62      column_set->AddColumn(views::GridLayout::FILL,
63                            views::GridLayout::FILL,
64                            0,
65                            views::GridLayout::USE_PREF,
66                            0,
67                            0);
68      break;
69
70    case DOUBLE_BUTTON_COLUMN_SET:
71      column_set->AddColumn(views::GridLayout::TRAILING,
72                            views::GridLayout::CENTER,
73                            1,
74                            views::GridLayout::USE_PREF,
75                            0,
76                            0);
77      column_set->AddPaddingColumn(0, views::kRelatedButtonHSpacing);
78      column_set->AddColumn(views::GridLayout::TRAILING,
79                            views::GridLayout::CENTER,
80                            0,
81                            views::GridLayout::USE_PREF,
82                            0,
83                            0);
84      break;
85    case LINK_BUTTON_COLUMN_SET:
86      column_set->AddColumn(views::GridLayout::LEADING,
87                            views::GridLayout::CENTER,
88                            1,
89                            views::GridLayout::USE_PREF,
90                            0,
91                            0);
92      column_set->AddPaddingColumn(0, views::kRelatedButtonHSpacing);
93      column_set->AddColumn(views::GridLayout::TRAILING,
94                            views::GridLayout::CENTER,
95                            0,
96                            views::GridLayout::USE_PREF,
97                            0,
98                            0);
99      break;
100  }
101  column_set->AddPaddingColumn(0, views::kPanelHorizMargin);
102}
103
104// Given a layout and a model, add an appropriate title using a
105// SINGLE_VIEW_COLUMN_SET, followed by a spacer row.
106void 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));
112
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);
118}
119
120}  // namespace
121
122
123// Globals --------------------------------------------------------------------
124
125namespace chrome {
126
127void ShowManagePasswordsBubble(content::WebContents* web_contents) {
128  ManagePasswordsUIController* controller =
129      ManagePasswordsUIController::FromWebContents(web_contents);
130  ManagePasswordsBubbleView::ShowBubble(
131      web_contents,
132      controller->state() ==
133              password_manager::ui::PENDING_PASSWORD_AND_BUBBLE_STATE
134          ? ManagePasswordsBubbleView::AUTOMATIC
135          : ManagePasswordsBubbleView::USER_ACTION);
136}
137
138}  // namespace chrome
139
140
141// ManagePasswordsBubbleView::PendingView -------------------------------------
142
143ManagePasswordsBubbleView::PendingView::PendingView(
144    ManagePasswordsBubbleView* parent)
145    : parent_(parent) {
146  views::GridLayout* layout = new views::GridLayout(this);
147  layout->set_minimum_size(gfx::Size(kDesiredBubbleWidth, 0));
148  SetLayoutManager(layout);
149
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));
159
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.
165
166  // Title row.
167  BuildColumnSet(layout, SINGLE_VIEW_COLUMN_SET);
168  AddTitleRow(layout, parent_->model());
169
170  // Credential row.
171  layout->StartRow(0, SINGLE_VIEW_COLUMN_SET);
172  layout->AddView(item);
173
174  // Button row.
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());
180
181  // Extra padding for visual awesomeness.
182  layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
183}
184
185ManagePasswordsBubbleView::PendingView::~PendingView() {
186}
187
188void ManagePasswordsBubbleView::PendingView::ButtonPressed(
189    views::Button* sender,
190    const ui::Event& event) {
191  DCHECK(sender == save_button_);
192  parent_->model()->OnSaveClicked();
193  parent_->Close();
194}
195
196void 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();
202      break;
203    case SavePasswordRefusalComboboxModel::INDEX_NEVER_FOR_THIS_SITE:
204      parent_->model()->OnNeverForThisSiteClicked();
205      break;
206  }
207  parent_->Close();
208}
209
210// ManagePasswordsBubbleView::ManageView --------------------------------------
211
212ManagePasswordsBubbleView::ManageView::ManageView(
213    ManagePasswordsBubbleView* parent)
214    : parent_(parent) {
215  views::GridLayout* layout = new views::GridLayout(this);
216  layout->set_minimum_size(gfx::Size(kDesiredBubbleWidth, 0));
217  SetLayoutManager(layout);
218
219  // Add the title.
220  BuildColumnSet(layout, SINGLE_VIEW_COLUMN_SET);
221  AddTitleRow(layout, parent_->model());
222
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();
230         ++i) {
231      ManagePasswordItemView* item = new ManagePasswordItemView(
232          parent_->model(),
233          *i->second,
234          i == parent_->model()->best_matches().begin()
235              ? ManagePasswordItemView::FIRST_ITEM
236              : ManagePasswordItemView::SUBSEQUENT_ITEM);
237
238      layout->StartRow(0, SINGLE_VIEW_COLUMN_SET);
239      layout->AddView(item);
240    }
241  } else {
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));
249
250    layout->StartRow(0, SINGLE_VIEW_COLUMN_SET);
251    layout->AddView(empty_label);
252    layout->AddPaddingRow(0, views::kRelatedControlSmallVerticalSpacing);
253  }
254
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);
262
263  done_button_ =
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));
268
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_);
274
275  // Extra padding for visual awesomeness.
276  layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
277}
278
279ManagePasswordsBubbleView::ManageView::~ManageView() {
280}
281
282void ManagePasswordsBubbleView::ManageView::ButtonPressed(
283    views::Button* sender,
284    const ui::Event& event) {
285  DCHECK(sender == done_button_);
286  parent_->model()->OnDoneClicked();
287  parent_->Close();
288}
289
290void ManagePasswordsBubbleView::ManageView::LinkClicked(views::Link* source,
291                                                        int event_flags) {
292  DCHECK_EQ(source, manage_link_);
293  parent_->model()->OnManageLinkClicked();
294  parent_->Close();
295}
296
297// ManagePasswordsBubbleView::BlacklistedView ---------------------------------
298
299ManagePasswordsBubbleView::BlacklistedView::BlacklistedView(
300    ManagePasswordsBubbleView* parent)
301    : parent_(parent) {
302  views::GridLayout* layout = new views::GridLayout(this);
303  layout->set_minimum_size(gfx::Size(kDesiredBubbleWidth, 0));
304  SetLayoutManager(layout);
305
306  // Add the title.
307  BuildColumnSet(layout, SINGLE_VIEW_COLUMN_SET);
308  AddTitleRow(layout, parent_->model());
309
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);
319
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));
326  done_button_ =
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));
331
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_);
337
338  // Extra padding for visual awesomeness.
339  layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
340}
341
342ManagePasswordsBubbleView::BlacklistedView::~BlacklistedView() {
343}
344
345void 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();
352  else
353    NOTREACHED();
354  parent_->Close();
355}
356
357// ManagePasswordsBubbleView --------------------------------------------------
358
359// static
360ManagePasswordsBubbleView* ManagePasswordsBubbleView::manage_passwords_bubble_ =
361    NULL;
362
363// static
364void ManagePasswordsBubbleView::ShowBubble(content::WebContents* web_contents,
365                                           DisplayReason reason) {
366  Browser* browser = chrome::FindBrowserWithWebContents(web_contents);
367  DCHECK(browser);
368  DCHECK(browser->window());
369  DCHECK(browser->fullscreen_controller());
370
371  if (IsShowing())
372    return;
373
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);
380
381  if (is_fullscreen) {
382    manage_passwords_bubble_->set_parent_window(
383        web_contents->GetTopLevelNativeWindow());
384  }
385
386  views::BubbleDelegateView::CreateBubble(manage_passwords_bubble_);
387
388  // Adjust for fullscreen after creation as it relies on the content size.
389  if (is_fullscreen) {
390    manage_passwords_bubble_->AdjustForFullscreen(
391        browser_view->GetBoundsInScreen());
392  }
393
394  manage_passwords_bubble_->GetWidget()->Show();
395  manage_passwords_bubble_->SetArrowPaintType(views::BubbleBorder::PAINT_NONE);
396}
397
398// static
399void ManagePasswordsBubbleView::CloseBubble() {
400  if (manage_passwords_bubble_)
401    manage_passwords_bubble_->Close();
402}
403
404// static
405bool ManagePasswordsBubbleView::IsShowing() {
406  // The bubble may be in the process of closing.
407  return (manage_passwords_bubble_ != NULL) &&
408      manage_passwords_bubble_->GetWidget()->IsVisible();
409}
410
411ManagePasswordsBubbleView::ManagePasswordsBubbleView(
412    content::WebContents* web_contents,
413    views::View* anchor_view,
414    DisplayReason reason)
415    : ManagePasswordsBubble(web_contents, reason),
416      BubbleDelegateView(anchor_view,
417                         anchor_view ? views::BubbleBorder::TOP_RIGHT
418                                     : views::BubbleBorder::NONE) {
419  // Compensate for built-in vertical padding in the anchor view's image.
420  set_anchor_view_insets(gfx::Insets(2, 0, 2, 0));
421  set_notify_enter_exit_on_child(true);
422}
423
424ManagePasswordsBubbleView::~ManagePasswordsBubbleView() {}
425
426void ManagePasswordsBubbleView::AdjustForFullscreen(
427    const gfx::Rect& screen_bounds) {
428  if (GetAnchorView())
429    return;
430
431  // The bubble's padding from the screen edge, used in fullscreen.
432  const int kFullscreenPaddingEnd = 20;
433  const size_t bubble_half_width = width() / 2;
434  const int x_pos = base::i18n::IsRTL() ?
435      screen_bounds.x() + bubble_half_width + kFullscreenPaddingEnd :
436      screen_bounds.right() - bubble_half_width - kFullscreenPaddingEnd;
437  SetAnchorRect(gfx::Rect(x_pos, screen_bounds.y(), 0, 0));
438}
439
440void ManagePasswordsBubbleView::Close() {
441  GetWidget()->Close();
442}
443
444void ManagePasswordsBubbleView::Init() {
445  views::FillLayout* layout = new views::FillLayout();
446  SetLayoutManager(layout);
447  SetFocusable(true);
448
449  if (password_manager::ui::IsPendingState(model()->state()))
450    AddChildView(new PendingView(this));
451  else if (model()->state() == password_manager::ui::BLACKLIST_STATE)
452    AddChildView(new BlacklistedView(this));
453  else
454    AddChildView(new ManageView(this));
455}
456
457void ManagePasswordsBubbleView::WindowClosing() {
458  // Close() closes the window asynchronously, so by the time we reach here,
459  // |manage_passwords_bubble_| may have already been reset.
460  if (manage_passwords_bubble_ == this)
461    manage_passwords_bubble_ = NULL;
462}
463