manage_passwords_bubble_view.cc revision 1320f92c476a1ad9d19dba2a48c72b75566198e9
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/ui/browser.h"
8#include "chrome/browser/ui/browser_finder.h"
9#include "chrome/browser/ui/passwords/manage_passwords_bubble_model.h"
10#include "chrome/browser/ui/passwords/manage_passwords_ui_controller.h"
11#include "chrome/browser/ui/passwords/save_password_refusal_combobox_model.h"
12#include "chrome/browser/ui/views/frame/browser_view.h"
13#include "chrome/browser/ui/views/location_bar/location_bar_view.h"
14#include "chrome/browser/ui/views/passwords/manage_password_item_view.h"
15#include "chrome/browser/ui/views/passwords/manage_passwords_icon_view.h"
16#include "chrome/grit/generated_resources.h"
17#include "content/public/browser/render_view_host.h"
18#include "content/public/browser/web_contents.h"
19#include "ui/aura/window.h"
20#include "ui/base/l10n/l10n_util.h"
21#include "ui/base/resource/resource_bundle.h"
22#include "ui/views/controls/button/blue_button.h"
23#include "ui/views/controls/button/label_button.h"
24#include "ui/views/controls/combobox/combobox.h"
25#include "ui/views/controls/combobox/combobox_listener.h"
26#include "ui/views/controls/link.h"
27#include "ui/views/controls/link_listener.h"
28#include "ui/views/controls/styled_label.h"
29#include "ui/views/controls/styled_label_listener.h"
30#include "ui/views/layout/fill_layout.h"
31#include "ui/views/layout/grid_layout.h"
32#include "ui/views/layout/layout_constants.h"
33
34
35// Helpers --------------------------------------------------------------------
36
37namespace {
38
39const int kDesiredBubbleWidth = 370;
40
41enum ColumnSetType {
42  // | | (FILL, FILL) | |
43  // Used for the bubble's header, the credentials list, and for simple
44  // messages like "No passwords".
45  SINGLE_VIEW_COLUMN_SET = 0,
46
47  // | | (TRAILING, CENTER) | | (TRAILING, CENTER) | |
48  // Used for buttons at the bottom of the bubble which should nest at the
49  // bottom-right corner.
50  DOUBLE_BUTTON_COLUMN_SET = 1,
51
52  // | | (LEADING, CENTER) | | (TRAILING, CENTER) | |
53  // Used for buttons at the bottom of the bubble which should occupy
54  // the corners.
55  LINK_BUTTON_COLUMN_SET = 2,
56
57  // | | (TRAILING, CENTER) | |
58  // Used when there is only one button which should next at the bottom-right
59  // corner.
60  SINGLE_BUTTON_COLUMN_SET = 3,
61};
62
63// Construct an appropriate ColumnSet for the given |type|, and add it
64// to |layout|.
65void BuildColumnSet(views::GridLayout* layout, ColumnSetType type) {
66  views::ColumnSet* column_set = layout->AddColumnSet(type);
67  column_set->AddPaddingColumn(0, views::kPanelHorizMargin);
68  int full_width = kDesiredBubbleWidth - (2 * views::kPanelHorizMargin);
69  switch (type) {
70    case SINGLE_VIEW_COLUMN_SET:
71      column_set->AddColumn(views::GridLayout::FILL,
72                            views::GridLayout::FILL,
73                            0,
74                            views::GridLayout::FIXED,
75                            full_width,
76                            0);
77      break;
78
79    case DOUBLE_BUTTON_COLUMN_SET:
80      column_set->AddColumn(views::GridLayout::TRAILING,
81                            views::GridLayout::CENTER,
82                            1,
83                            views::GridLayout::USE_PREF,
84                            0,
85                            0);
86      column_set->AddPaddingColumn(0, views::kRelatedButtonHSpacing);
87      column_set->AddColumn(views::GridLayout::TRAILING,
88                            views::GridLayout::CENTER,
89                            0,
90                            views::GridLayout::USE_PREF,
91                            0,
92                            0);
93      break;
94    case LINK_BUTTON_COLUMN_SET:
95      column_set->AddColumn(views::GridLayout::LEADING,
96                            views::GridLayout::CENTER,
97                            1,
98                            views::GridLayout::USE_PREF,
99                            0,
100                            0);
101      column_set->AddPaddingColumn(0, views::kRelatedButtonHSpacing);
102      column_set->AddColumn(views::GridLayout::TRAILING,
103                            views::GridLayout::CENTER,
104                            0,
105                            views::GridLayout::USE_PREF,
106                            0,
107                            0);
108      break;
109    case SINGLE_BUTTON_COLUMN_SET:
110      column_set->AddColumn(views::GridLayout::TRAILING,
111                            views::GridLayout::CENTER,
112                            1,
113                            views::GridLayout::USE_PREF,
114                            0,
115                            0);
116  }
117  column_set->AddPaddingColumn(0, views::kPanelHorizMargin);
118}
119
120// Given a layout and a model, add an appropriate title using a
121// SINGLE_VIEW_COLUMN_SET, followed by a spacer row.
122void AddTitleRow(views::GridLayout* layout, ManagePasswordsBubbleModel* model) {
123  views::Label* title_label = new views::Label(model->title());
124  title_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
125  title_label->SetMultiLine(true);
126  title_label->SetFontList(ui::ResourceBundle::GetSharedInstance().GetFontList(
127      ui::ResourceBundle::MediumFont));
128
129  // Add the title to the layout with appropriate padding.
130  layout->StartRowWithPadding(
131      0, SINGLE_VIEW_COLUMN_SET, 0, views::kRelatedControlSmallVerticalSpacing);
132  layout->AddView(title_label);
133  layout->AddPaddingRow(0, views::kUnrelatedControlVerticalSpacing);
134}
135
136}  // namespace
137
138
139// Globals --------------------------------------------------------------------
140
141namespace chrome {
142
143void ShowManagePasswordsBubble(content::WebContents* web_contents) {
144  if (ManagePasswordsBubbleView::IsShowing()) {
145    // The bubble is currently shown for some other tab. We should close it now
146    // and open for |web_contents|.
147    ManagePasswordsBubbleView::CloseBubble();
148  }
149  ManagePasswordsUIController* controller =
150      ManagePasswordsUIController::FromWebContents(web_contents);
151  ManagePasswordsBubbleView::ShowBubble(
152      web_contents,
153      password_manager::ui::IsAutomaticDisplayState(controller->state())
154          ? ManagePasswordsBubbleView::AUTOMATIC
155          : ManagePasswordsBubbleView::USER_ACTION);
156}
157
158void CloseManagePasswordsBubble(content::WebContents* web_contents) {
159  if (!ManagePasswordsBubbleView::IsShowing())
160    return;
161  content::WebContents* bubble_web_contents =
162      ManagePasswordsBubbleView::manage_password_bubble()->web_contents();
163  if (web_contents == bubble_web_contents)
164    ManagePasswordsBubbleView::CloseBubble();
165}
166
167}  // namespace chrome
168
169
170// ManagePasswordsBubbleView::PendingView -------------------------------------
171
172// A view offering the user the ability to save credentials. Contains a
173// single ManagePasswordItemView, along with a "Save Passwords" button
174// and a rejection combobox.
175class ManagePasswordsBubbleView::PendingView : public views::View,
176                                               public views::ButtonListener,
177                                               public views::ComboboxListener {
178 public:
179  explicit PendingView(ManagePasswordsBubbleView* parent);
180  virtual ~PendingView();
181
182 private:
183  // views::ButtonListener:
184  virtual void ButtonPressed(views::Button* sender,
185                             const ui::Event& event) OVERRIDE;
186
187  // Handles the event when the user changes an index of a combobox.
188  virtual void OnPerformAction(views::Combobox* source) OVERRIDE;
189
190  ManagePasswordsBubbleView* parent_;
191
192  views::BlueButton* save_button_;
193
194  // The combobox doesn't take ownership of its model. If we created a
195  // combobox we need to ensure that we delete the model here, and because the
196  // combobox uses the model in it's destructor, we need to make sure we
197  // delete the model _after_ the combobox itself is deleted.
198  scoped_ptr<SavePasswordRefusalComboboxModel> combobox_model_;
199  scoped_ptr<views::Combobox> refuse_combobox_;
200};
201
202ManagePasswordsBubbleView::PendingView::PendingView(
203    ManagePasswordsBubbleView* parent)
204    : parent_(parent) {
205  views::GridLayout* layout = new views::GridLayout(this);
206  layout->set_minimum_size(gfx::Size(kDesiredBubbleWidth, 0));
207  SetLayoutManager(layout);
208
209  // Create the pending credential item, save button and refusal combobox.
210  ManagePasswordItemView* item =
211      new ManagePasswordItemView(parent->model(),
212                                 parent->model()->pending_credentials(),
213                                 password_manager::ui::FIRST_ITEM);
214  save_button_ = new views::BlueButton(
215      this, l10n_util::GetStringUTF16(IDS_PASSWORD_MANAGER_SAVE_BUTTON));
216  save_button_->SetFontList(ui::ResourceBundle::GetSharedInstance().GetFontList(
217      ui::ResourceBundle::SmallFont));
218
219  combobox_model_.reset(new SavePasswordRefusalComboboxModel());
220  refuse_combobox_.reset(new views::Combobox(combobox_model_.get()));
221  refuse_combobox_->set_listener(this);
222  refuse_combobox_->SetStyle(views::Combobox::STYLE_ACTION);
223  // TODO(mkwst): Need a mechanism to pipe a font list down into a combobox.
224
225  // Title row.
226  BuildColumnSet(layout, SINGLE_VIEW_COLUMN_SET);
227  AddTitleRow(layout, parent_->model());
228
229  // Credential row.
230  layout->StartRow(0, SINGLE_VIEW_COLUMN_SET);
231  layout->AddView(item);
232
233  // Button row.
234  BuildColumnSet(layout, DOUBLE_BUTTON_COLUMN_SET);
235  layout->StartRowWithPadding(
236      0, DOUBLE_BUTTON_COLUMN_SET, 0, views::kRelatedControlVerticalSpacing);
237  layout->AddView(save_button_);
238  layout->AddView(refuse_combobox_.get());
239
240  // Extra padding for visual awesomeness.
241  layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
242
243  parent_->set_initially_focused_view(save_button_);
244}
245
246ManagePasswordsBubbleView::PendingView::~PendingView() {
247}
248
249void ManagePasswordsBubbleView::PendingView::ButtonPressed(
250    views::Button* sender,
251    const ui::Event& event) {
252  DCHECK(sender == save_button_);
253  parent_->model()->OnSaveClicked();
254  parent_->Close();
255}
256
257void ManagePasswordsBubbleView::PendingView::OnPerformAction(
258    views::Combobox* source) {
259  DCHECK_EQ(source, refuse_combobox_);
260  switch (refuse_combobox_->selected_index()) {
261    case SavePasswordRefusalComboboxModel::INDEX_NOPE:
262      parent_->model()->OnNopeClicked();
263      parent_->Close();
264      break;
265    case SavePasswordRefusalComboboxModel::INDEX_NEVER_FOR_THIS_SITE:
266      parent_->NotifyNeverForThisSiteClicked();
267      break;
268  }
269}
270
271// ManagePasswordsBubbleView::ConfirmNeverView ---------------------------------
272
273// A view offering the user the ability to undo her decision to never save
274// passwords for a particular site.
275class ManagePasswordsBubbleView::ConfirmNeverView
276    : public views::View,
277      public views::ButtonListener {
278 public:
279  explicit ConfirmNeverView(ManagePasswordsBubbleView* parent);
280  virtual ~ConfirmNeverView();
281
282 private:
283  // views::ButtonListener:
284  virtual void ButtonPressed(views::Button* sender,
285                             const ui::Event& event) OVERRIDE;
286
287  ManagePasswordsBubbleView* parent_;
288
289  views::LabelButton* confirm_button_;
290  views::LabelButton* undo_button_;
291};
292
293ManagePasswordsBubbleView::ConfirmNeverView::ConfirmNeverView(
294    ManagePasswordsBubbleView* parent)
295    : parent_(parent) {
296  views::GridLayout* layout = new views::GridLayout(this);
297  layout->set_minimum_size(gfx::Size(kDesiredBubbleWidth, 0));
298  SetLayoutManager(layout);
299
300  // Title row.
301  BuildColumnSet(layout, SINGLE_VIEW_COLUMN_SET);
302  views::Label* title_label = new views::Label(l10n_util::GetStringUTF16(
303      IDS_MANAGE_PASSWORDS_BLACKLIST_CONFIRMATION_TITLE));
304  title_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
305  title_label->SetMultiLine(true);
306  title_label->SetFontList(ui::ResourceBundle::GetSharedInstance().GetFontList(
307      ui::ResourceBundle::MediumFont));
308  layout->StartRowWithPadding(
309      0, SINGLE_VIEW_COLUMN_SET, 0, views::kRelatedControlSmallVerticalSpacing);
310  layout->AddView(title_label);
311  layout->AddPaddingRow(0, views::kUnrelatedControlVerticalSpacing);
312
313  // Confirmation text.
314  views::Label* confirmation = new views::Label(l10n_util::GetStringUTF16(
315      IDS_MANAGE_PASSWORDS_BLACKLIST_CONFIRMATION_TEXT));
316  confirmation->SetHorizontalAlignment(gfx::ALIGN_LEFT);
317  confirmation->SetMultiLine(true);
318  confirmation->SetFontList(ui::ResourceBundle::GetSharedInstance().GetFontList(
319      ui::ResourceBundle::SmallFont));
320  layout->StartRow(0, SINGLE_VIEW_COLUMN_SET);
321  layout->AddView(confirmation);
322  layout->AddPaddingRow(0, views::kRelatedControlSmallVerticalSpacing);
323
324  // Confirm and undo buttons.
325  BuildColumnSet(layout, DOUBLE_BUTTON_COLUMN_SET);
326  layout->StartRowWithPadding(
327      0, DOUBLE_BUTTON_COLUMN_SET, 0, views::kRelatedControlVerticalSpacing);
328
329  confirm_button_ = new views::LabelButton(
330      this,
331      l10n_util::GetStringUTF16(
332          IDS_MANAGE_PASSWORDS_BLACKLIST_CONFIRMATION_BUTTON));
333  confirm_button_->SetStyle(views::Button::STYLE_BUTTON);
334  confirm_button_->SetFontList(
335      ui::ResourceBundle::GetSharedInstance().GetFontList(
336          ui::ResourceBundle::SmallFont));
337  layout->AddView(confirm_button_);
338
339  undo_button_ =
340      new views::LabelButton(this, l10n_util::GetStringUTF16(IDS_CANCEL));
341  undo_button_->SetStyle(views::Button::STYLE_BUTTON);
342  undo_button_->SetFontList(ui::ResourceBundle::GetSharedInstance().GetFontList(
343      ui::ResourceBundle::SmallFont));
344  layout->AddView(undo_button_);
345
346  // Extra padding for visual awesomeness.
347  layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
348
349  parent_->set_initially_focused_view(confirm_button_);
350}
351
352ManagePasswordsBubbleView::ConfirmNeverView::~ConfirmNeverView() {
353}
354
355void ManagePasswordsBubbleView::ConfirmNeverView::ButtonPressed(
356    views::Button* sender,
357    const ui::Event& event) {
358  DCHECK(sender == confirm_button_ || sender == undo_button_);
359  if (sender == confirm_button_)
360    parent_->NotifyConfirmedNeverForThisSite();
361  else
362    parent_->NotifyUndoNeverForThisSite();
363}
364
365// ManagePasswordsBubbleView::ManageView --------------------------------------
366
367// A view offering the user a list of her currently saved credentials
368// for the current page, along with a "Manage passwords" link and a
369// "Done" button.
370class ManagePasswordsBubbleView::ManageView : public views::View,
371                                              public views::ButtonListener,
372                                              public views::LinkListener {
373 public:
374  explicit ManageView(ManagePasswordsBubbleView* parent);
375  virtual ~ManageView();
376
377 private:
378  // views::ButtonListener:
379  virtual void ButtonPressed(views::Button* sender,
380                             const ui::Event& event) OVERRIDE;
381
382  // views::LinkListener:
383  virtual void LinkClicked(views::Link* source, int event_flags) OVERRIDE;
384
385  ManagePasswordsBubbleView* parent_;
386
387  views::Link* manage_link_;
388  views::LabelButton* done_button_;
389};
390
391ManagePasswordsBubbleView::ManageView::ManageView(
392    ManagePasswordsBubbleView* parent)
393    : parent_(parent) {
394  views::GridLayout* layout = new views::GridLayout(this);
395  layout->set_minimum_size(gfx::Size(kDesiredBubbleWidth, 0));
396  SetLayoutManager(layout);
397
398  // Add the title.
399  BuildColumnSet(layout, SINGLE_VIEW_COLUMN_SET);
400  AddTitleRow(layout, parent_->model());
401
402  // If we have a list of passwords to store for the current site, display
403  // them to the user for management. Otherwise, render a "No passwords for
404  // this site" message.
405  if (!parent_->model()->best_matches().empty()) {
406    for (autofill::ConstPasswordFormMap::const_iterator i(
407             parent_->model()->best_matches().begin());
408         i != parent_->model()->best_matches().end();
409         ++i) {
410      ManagePasswordItemView* item = new ManagePasswordItemView(
411          parent_->model(),
412          *i->second,
413          i == parent_->model()->best_matches().begin()
414              ? password_manager::ui::FIRST_ITEM
415              : password_manager::ui::SUBSEQUENT_ITEM);
416
417      layout->StartRow(0, SINGLE_VIEW_COLUMN_SET);
418      layout->AddView(item);
419    }
420  } else {
421    views::Label* empty_label = new views::Label(
422        l10n_util::GetStringUTF16(IDS_MANAGE_PASSWORDS_NO_PASSWORDS));
423    empty_label->SetMultiLine(true);
424    empty_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
425    empty_label->SetFontList(
426        ui::ResourceBundle::GetSharedInstance().GetFontList(
427            ui::ResourceBundle::SmallFont));
428
429    layout->StartRow(0, SINGLE_VIEW_COLUMN_SET);
430    layout->AddView(empty_label);
431    layout->AddPaddingRow(0, views::kRelatedControlSmallVerticalSpacing);
432  }
433
434  // Then add the "manage passwords" link and "Done" button.
435  manage_link_ = new views::Link(parent_->model()->manage_link());
436  manage_link_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
437  manage_link_->SetFontList(ui::ResourceBundle::GetSharedInstance().GetFontList(
438      ui::ResourceBundle::SmallFont));
439  manage_link_->SetUnderline(false);
440  manage_link_->set_listener(this);
441
442  done_button_ =
443      new views::LabelButton(this, l10n_util::GetStringUTF16(IDS_DONE));
444  done_button_->SetStyle(views::Button::STYLE_BUTTON);
445  done_button_->SetFontList(ui::ResourceBundle::GetSharedInstance().GetFontList(
446      ui::ResourceBundle::SmallFont));
447
448  BuildColumnSet(layout, LINK_BUTTON_COLUMN_SET);
449  layout->StartRowWithPadding(
450      0, LINK_BUTTON_COLUMN_SET, 0, views::kRelatedControlVerticalSpacing);
451  layout->AddView(manage_link_);
452  layout->AddView(done_button_);
453
454  // Extra padding for visual awesomeness.
455  layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
456
457  parent_->set_initially_focused_view(done_button_);
458}
459
460ManagePasswordsBubbleView::ManageView::~ManageView() {
461}
462
463void ManagePasswordsBubbleView::ManageView::ButtonPressed(
464    views::Button* sender,
465    const ui::Event& event) {
466  DCHECK(sender == done_button_);
467  parent_->model()->OnDoneClicked();
468  parent_->Close();
469}
470
471void ManagePasswordsBubbleView::ManageView::LinkClicked(views::Link* source,
472                                                        int event_flags) {
473  DCHECK_EQ(source, manage_link_);
474  parent_->model()->OnManageLinkClicked();
475  parent_->Close();
476}
477
478// ManagePasswordsBubbleView::BlacklistedView ---------------------------------
479
480// A view offering the user the ability to re-enable the password manager for
481// a specific site after she's decided to "never save passwords".
482class ManagePasswordsBubbleView::BlacklistedView
483    : public views::View,
484      public views::ButtonListener {
485 public:
486  explicit BlacklistedView(ManagePasswordsBubbleView* parent);
487  virtual ~BlacklistedView();
488
489 private:
490  // views::ButtonListener:
491  virtual void ButtonPressed(views::Button* sender,
492                             const ui::Event& event) OVERRIDE;
493
494  ManagePasswordsBubbleView* parent_;
495
496  views::BlueButton* unblacklist_button_;
497  views::LabelButton* done_button_;
498};
499
500ManagePasswordsBubbleView::BlacklistedView::BlacklistedView(
501    ManagePasswordsBubbleView* parent)
502    : parent_(parent) {
503  views::GridLayout* layout = new views::GridLayout(this);
504  layout->set_minimum_size(gfx::Size(kDesiredBubbleWidth, 0));
505  SetLayoutManager(layout);
506
507  // Add the title.
508  BuildColumnSet(layout, SINGLE_VIEW_COLUMN_SET);
509  AddTitleRow(layout, parent_->model());
510
511  // Add the "Hey! You blacklisted this site!" text.
512  views::Label* blacklisted = new views::Label(
513      l10n_util::GetStringUTF16(IDS_MANAGE_PASSWORDS_BLACKLISTED));
514  blacklisted->SetMultiLine(true);
515  blacklisted->SetFontList(ui::ResourceBundle::GetSharedInstance().GetFontList(
516      ui::ResourceBundle::SmallFont));
517  layout->StartRow(0, SINGLE_VIEW_COLUMN_SET);
518  layout->AddView(blacklisted);
519  layout->AddPaddingRow(0, views::kRelatedControlSmallVerticalSpacing);
520
521  // Then add the "enable password manager" and "Done" buttons.
522  unblacklist_button_ = new views::BlueButton(
523      this, l10n_util::GetStringUTF16(IDS_PASSWORD_MANAGER_UNBLACKLIST_BUTTON));
524  unblacklist_button_->SetFontList(
525      ui::ResourceBundle::GetSharedInstance().GetFontList(
526          ui::ResourceBundle::SmallFont));
527  done_button_ =
528      new views::LabelButton(this, l10n_util::GetStringUTF16(IDS_DONE));
529  done_button_->SetStyle(views::Button::STYLE_BUTTON);
530  done_button_->SetFontList(ui::ResourceBundle::GetSharedInstance().GetFontList(
531      ui::ResourceBundle::SmallFont));
532
533  BuildColumnSet(layout, DOUBLE_BUTTON_COLUMN_SET);
534  layout->StartRowWithPadding(
535      0, DOUBLE_BUTTON_COLUMN_SET, 0, views::kRelatedControlVerticalSpacing);
536  layout->AddView(unblacklist_button_);
537  layout->AddView(done_button_);
538
539  // Extra padding for visual awesomeness.
540  layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
541
542  parent_->set_initially_focused_view(unblacklist_button_);
543}
544
545ManagePasswordsBubbleView::BlacklistedView::~BlacklistedView() {
546}
547
548void ManagePasswordsBubbleView::BlacklistedView::ButtonPressed(
549    views::Button* sender,
550    const ui::Event& event) {
551  if (sender == done_button_)
552    parent_->model()->OnDoneClicked();
553  else if (sender == unblacklist_button_)
554    parent_->model()->OnUnblacklistClicked();
555  else
556    NOTREACHED();
557  parent_->Close();
558}
559
560// ManagePasswordsBubbleView::SaveConfirmationView ----------------------------
561
562// A view confirming to the user that a password was saved and offering a link
563// to the Google account manager.
564class ManagePasswordsBubbleView::SaveConfirmationView
565    : public views::View,
566      public views::ButtonListener,
567      public views::StyledLabelListener {
568 public:
569  explicit SaveConfirmationView(ManagePasswordsBubbleView* parent);
570  virtual ~SaveConfirmationView();
571
572 private:
573  // views::ButtonListener:
574  virtual void ButtonPressed(views::Button* sender,
575                             const ui::Event& event) OVERRIDE;
576
577  // views::StyledLabelListener implementation
578  virtual void StyledLabelLinkClicked(const gfx::Range& range,
579                                      int event_flags) OVERRIDE;
580
581  ManagePasswordsBubbleView* parent_;
582
583  views::LabelButton* ok_button_;
584};
585
586ManagePasswordsBubbleView::SaveConfirmationView::SaveConfirmationView(
587    ManagePasswordsBubbleView* parent)
588    : parent_(parent) {
589  views::GridLayout* layout = new views::GridLayout(this);
590  layout->set_minimum_size(gfx::Size(kDesiredBubbleWidth, 0));
591  SetLayoutManager(layout);
592
593  BuildColumnSet(layout, SINGLE_VIEW_COLUMN_SET);
594  AddTitleRow(layout, parent_->model());
595
596  views::StyledLabel* confirmation =
597      new views::StyledLabel(parent_->model()->save_confirmation_text(), this);
598  confirmation->SetBaseFontList(
599      ui::ResourceBundle::GetSharedInstance().GetFontList(
600          ui::ResourceBundle::SmallFont));
601  confirmation->AddStyleRange(
602      parent_->model()->save_confirmation_link_range(),
603      views::StyledLabel::RangeStyleInfo::CreateForLink());
604
605  layout->StartRow(0, SINGLE_VIEW_COLUMN_SET);
606  layout->AddView(confirmation);
607
608  ok_button_ = new views::LabelButton(this, l10n_util::GetStringUTF16(IDS_OK));
609  ok_button_->SetStyle(views::Button::STYLE_BUTTON);
610  ok_button_->SetFontList(ui::ResourceBundle::GetSharedInstance().GetFontList(
611      ui::ResourceBundle::SmallFont));
612
613  BuildColumnSet(layout, SINGLE_BUTTON_COLUMN_SET);
614  layout->StartRowWithPadding(
615      0, SINGLE_BUTTON_COLUMN_SET, 0, views::kRelatedControlVerticalSpacing);
616  layout->AddView(ok_button_);
617
618  // Extra padding for visual awesomeness.
619  layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
620
621  parent_->set_initially_focused_view(ok_button_);
622}
623
624ManagePasswordsBubbleView::SaveConfirmationView::~SaveConfirmationView() {
625}
626
627void ManagePasswordsBubbleView::SaveConfirmationView::StyledLabelLinkClicked(
628    const gfx::Range& range, int event_flags) {
629  DCHECK_EQ(range, parent_->model()->save_confirmation_link_range());
630  parent_->model()->OnManageLinkClicked();
631  parent_->Close();
632}
633
634void ManagePasswordsBubbleView::SaveConfirmationView::ButtonPressed(
635    views::Button* sender, const ui::Event& event) {
636  DCHECK_EQ(sender, ok_button_);
637  parent_->model()->OnOKClicked();
638  parent_->Close();
639}
640
641// ManagePasswordsBubbleView::WebContentMouseHandler --------------------------
642
643// The class listens for WebContentsView events and notifies the bubble if the
644// view was clicked on or received keystrokes.
645class ManagePasswordsBubbleView::WebContentMouseHandler
646    : public ui::EventHandler {
647 public:
648  explicit WebContentMouseHandler(ManagePasswordsBubbleView* bubble);
649  virtual ~WebContentMouseHandler();
650
651  virtual void OnKeyEvent(ui::KeyEvent* event) OVERRIDE;
652  virtual void OnMouseEvent(ui::MouseEvent* event) OVERRIDE;
653
654 private:
655  aura::Window* GetWebContentsWindow();
656
657  ManagePasswordsBubbleView* bubble_;
658
659  DISALLOW_COPY_AND_ASSIGN(WebContentMouseHandler);
660};
661
662ManagePasswordsBubbleView::WebContentMouseHandler::WebContentMouseHandler(
663    ManagePasswordsBubbleView* bubble)
664    : bubble_(bubble) {
665  GetWebContentsWindow()->AddPreTargetHandler(this);
666}
667
668ManagePasswordsBubbleView::WebContentMouseHandler::~WebContentMouseHandler() {
669  if (aura::Window* window = GetWebContentsWindow())
670    window->RemovePreTargetHandler(this);
671}
672
673void ManagePasswordsBubbleView::WebContentMouseHandler::OnKeyEvent(
674    ui::KeyEvent* event) {
675  content::WebContents* web_contents = bubble_->model()->web_contents();
676  content::RenderViewHost* rvh = web_contents->GetRenderViewHost();
677  if (rvh->IsFocusedElementEditable() &&
678      event->type() == ui::ET_KEY_PRESSED)
679    bubble_->Close();
680}
681
682void ManagePasswordsBubbleView::WebContentMouseHandler::OnMouseEvent(
683    ui::MouseEvent* event) {
684  if (event->type() == ui::ET_MOUSE_PRESSED)
685    bubble_->Close();
686}
687
688aura::Window*
689ManagePasswordsBubbleView::WebContentMouseHandler::GetWebContentsWindow() {
690  content::WebContents* web_contents = bubble_->model()->web_contents();
691  return web_contents ? web_contents->GetNativeView() : NULL;
692}
693
694// ManagePasswordsBubbleView --------------------------------------------------
695
696// static
697ManagePasswordsBubbleView* ManagePasswordsBubbleView::manage_passwords_bubble_ =
698    NULL;
699
700// static
701void ManagePasswordsBubbleView::ShowBubble(content::WebContents* web_contents,
702                                           DisplayReason reason) {
703  Browser* browser = chrome::FindBrowserWithWebContents(web_contents);
704  DCHECK(browser);
705  DCHECK(browser->window());
706  DCHECK(browser->fullscreen_controller());
707
708  if (IsShowing())
709    return;
710
711  BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser);
712  bool is_fullscreen = browser_view->IsFullscreen();
713  ManagePasswordsIconView* anchor_view =
714      is_fullscreen
715          ? NULL
716          : browser_view->GetLocationBarView()->manage_passwords_icon_view();
717  manage_passwords_bubble_ = new ManagePasswordsBubbleView(
718      web_contents, anchor_view, reason);
719
720  if (is_fullscreen) {
721    manage_passwords_bubble_->set_parent_window(
722        web_contents->GetTopLevelNativeWindow());
723  }
724
725  views::BubbleDelegateView::CreateBubble(manage_passwords_bubble_);
726
727  // Adjust for fullscreen after creation as it relies on the content size.
728  if (is_fullscreen) {
729    manage_passwords_bubble_->AdjustForFullscreen(
730        browser_view->GetBoundsInScreen());
731  }
732  if (reason == AUTOMATIC)
733    manage_passwords_bubble_->GetWidget()->ShowInactive();
734  else
735    manage_passwords_bubble_->GetWidget()->Show();
736}
737
738// static
739void ManagePasswordsBubbleView::CloseBubble() {
740  if (manage_passwords_bubble_)
741    manage_passwords_bubble_->Close();
742}
743
744// static
745void ManagePasswordsBubbleView::ActivateBubble() {
746  if (!IsShowing())
747    return;
748  manage_passwords_bubble_->GetWidget()->Activate();
749}
750
751// static
752bool ManagePasswordsBubbleView::IsShowing() {
753  // The bubble may be in the process of closing.
754  return (manage_passwords_bubble_ != NULL) &&
755      manage_passwords_bubble_->GetWidget()->IsVisible();
756}
757
758content::WebContents* ManagePasswordsBubbleView::web_contents() const {
759  return model()->web_contents();
760}
761
762ManagePasswordsBubbleView::ManagePasswordsBubbleView(
763    content::WebContents* web_contents,
764    ManagePasswordsIconView* anchor_view,
765    DisplayReason reason)
766    : ManagePasswordsBubble(web_contents, reason),
767      BubbleDelegateView(anchor_view,
768                         anchor_view ? views::BubbleBorder::TOP_RIGHT
769                                     : views::BubbleBorder::NONE),
770      anchor_view_(anchor_view),
771      never_save_passwords_(false),
772      initially_focused_view_(NULL) {
773  // Compensate for built-in vertical padding in the anchor view's image.
774  set_anchor_view_insets(gfx::Insets(5, 0, 5, 0));
775  if (anchor_view)
776    anchor_view->SetActive(true);
777  mouse_handler_.reset(new WebContentMouseHandler(this));
778}
779
780ManagePasswordsBubbleView::~ManagePasswordsBubbleView() {
781  if (anchor_view_)
782    anchor_view_->SetActive(false);
783}
784
785void ManagePasswordsBubbleView::AdjustForFullscreen(
786    const gfx::Rect& screen_bounds) {
787  if (GetAnchorView())
788    return;
789
790  // The bubble's padding from the screen edge, used in fullscreen.
791  const int kFullscreenPaddingEnd = 20;
792  const size_t bubble_half_width = width() / 2;
793  const int x_pos = base::i18n::IsRTL() ?
794      screen_bounds.x() + bubble_half_width + kFullscreenPaddingEnd :
795      screen_bounds.right() - bubble_half_width - kFullscreenPaddingEnd;
796  SetAnchorRect(gfx::Rect(x_pos, screen_bounds.y(), 0, 0));
797}
798
799void ManagePasswordsBubbleView::Close() {
800  mouse_handler_.reset();
801  GetWidget()->Close();
802}
803
804void ManagePasswordsBubbleView::Refresh() {
805  RemoveAllChildViews(true);
806  initially_focused_view_ = NULL;
807  if (password_manager::ui::IsPendingState(model()->state())) {
808    if (never_save_passwords_)
809      AddChildView(new ConfirmNeverView(this));
810    else
811      AddChildView(new PendingView(this));
812  } else if (model()->state() == password_manager::ui::BLACKLIST_STATE) {
813    AddChildView(new BlacklistedView(this));
814  } else if (model()->state() == password_manager::ui::CONFIRMATION_STATE) {
815    AddChildView(new SaveConfirmationView(this));
816  } else {
817    AddChildView(new ManageView(this));
818  }
819  GetLayoutManager()->Layout(this);
820}
821
822void ManagePasswordsBubbleView::NotifyNeverForThisSiteClicked() {
823  if (model()->best_matches().empty()) {
824    // Skip confirmation if there are no existing passwords for this site.
825    NotifyConfirmedNeverForThisSite();
826  } else {
827    never_save_passwords_ = true;
828    Refresh();
829  }
830}
831
832void ManagePasswordsBubbleView::NotifyConfirmedNeverForThisSite() {
833  model()->OnNeverForThisSiteClicked();
834  Close();
835}
836
837void ManagePasswordsBubbleView::NotifyUndoNeverForThisSite() {
838  never_save_passwords_ = false;
839  Refresh();
840}
841
842void ManagePasswordsBubbleView::Init() {
843  views::FillLayout* layout = new views::FillLayout();
844  SetLayoutManager(layout);
845
846  Refresh();
847}
848
849void ManagePasswordsBubbleView::WindowClosing() {
850  // Close() closes the window asynchronously, so by the time we reach here,
851  // |manage_passwords_bubble_| may have already been reset.
852  if (manage_passwords_bubble_ == this)
853    manage_passwords_bubble_ = NULL;
854}
855
856views::View* ManagePasswordsBubbleView::GetInitiallyFocusedView() {
857  return initially_focused_view_;
858}
859