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