| // Copyright 2013 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/ui/views/passwords/manage_password_items_view.h" |
| |
| #include <numeric> |
| |
| #include "base/macros.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "chrome/app/vector_icons/vector_icons.h" |
| #include "chrome/browser/ui/passwords/manage_passwords_bubble_model.h" |
| #include "chrome/browser/ui/passwords/manage_passwords_view_utils.h" |
| #include "chrome/browser/ui/views/harmony/chrome_layout_provider.h" |
| #include "chrome/browser/ui/views/harmony/chrome_typography.h" |
| #include "chrome/grit/generated_resources.h" |
| #include "components/password_manager/core/common/password_manager_ui.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/base/resource/resource_bundle.h" |
| #include "ui/gfx/range/range.h" |
| #include "ui/resources/grit/ui_resources.h" |
| #include "ui/views/controls/button/button.h" |
| #include "ui/views/controls/button/image_button.h" |
| #include "ui/views/controls/button/image_button_factory.h" |
| #include "ui/views/controls/button/md_text_button.h" |
| #include "ui/views/controls/label.h" |
| #include "ui/views/controls/link.h" |
| #include "ui/views/controls/link_listener.h" |
| #include "ui/views/controls/textfield/textfield.h" |
| #include "ui/views/layout/fill_layout.h" |
| #include "ui/views/layout/grid_layout.h" |
| |
| namespace { |
| |
| constexpr int kDeleteButtonTag = 1; |
| constexpr int kUndoButtonTag = 2; |
| |
| // Column set identifiers for displaying or undoing removal of credentials. |
| // They both allocate space differently. |
| enum ColumnSetType { PASSWORD_COLUMN_SET, UNDO_COLUMN_SET }; |
| |
| void BuildColumnSet(views::GridLayout* layout, ColumnSetType type_id) { |
| DCHECK(!layout->GetColumnSet(type_id)); |
| views::ColumnSet* column_set = layout->AddColumnSet(type_id); |
| // Passwords are split 60/40 (6:4) as the username is more important |
| // than obscured password digits. Otherwise two columns are 50/50 (1:1). |
| constexpr float kFirstColumnWeight = 60.0f; |
| constexpr float kSecondColumnWeight = 40.0f; |
| const int between_column_padding = |
| ChromeLayoutProvider::Get()->GetDistanceMetric( |
| views::DISTANCE_RELATED_CONTROL_HORIZONTAL); |
| column_set->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, |
| kFirstColumnWeight, views::GridLayout::FIXED, 0, 0); |
| |
| if (type_id == PASSWORD_COLUMN_SET) { |
| column_set->AddPaddingColumn(0, between_column_padding); |
| column_set->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, |
| kSecondColumnWeight, views::GridLayout::FIXED, 0, 0); |
| } |
| // All rows end with a trailing column for the undo/trash button. |
| column_set->AddPaddingColumn(0, between_column_padding); |
| column_set->AddColumn(views::GridLayout::TRAILING, views::GridLayout::FILL, 0, |
| views::GridLayout::USE_PREF, 0, 0); |
| } |
| |
| void StartRow(views::GridLayout* layout, ColumnSetType type_id) { |
| if (!layout->GetColumnSet(type_id)) |
| BuildColumnSet(layout, type_id); |
| layout->StartRow(0, type_id); |
| } |
| |
| std::unique_ptr<views::ImageButton> CreateDeleteButton( |
| views::ButtonListener* listener, |
| const base::string16& username) { |
| std::unique_ptr<views::ImageButton> button( |
| views::CreateVectorImageButton(listener)); |
| views::SetImageFromVectorIcon(button.get(), kTrashCanIcon); |
| button->SetFocusForPlatform(); |
| button->SetTooltipText( |
| l10n_util::GetStringFUTF16(IDS_MANAGE_PASSWORDS_DELETE, username)); |
| button->set_tag(kDeleteButtonTag); |
| return button; |
| } |
| |
| std::unique_ptr<views::LabelButton> CreateUndoButton( |
| views::ButtonListener* listener, |
| const base::string16& username) { |
| std::unique_ptr<views::LabelButton> undo_button( |
| views::MdTextButton::CreateSecondaryUiButton( |
| listener, l10n_util::GetStringUTF16(IDS_MANAGE_PASSWORDS_UNDO))); |
| undo_button->set_tag(kUndoButtonTag); |
| undo_button->SetFocusForPlatform(); |
| undo_button->SetTooltipText( |
| l10n_util::GetStringFUTF16(IDS_MANAGE_PASSWORDS_UNDO_TOOLTIP, username)); |
| return undo_button; |
| } |
| |
| } // namespace |
| |
| std::unique_ptr<views::Label> CreateUsernameLabel( |
| const autofill::PasswordForm& form) { |
| auto label = std::make_unique<views::Label>( |
| GetDisplayUsername(form), CONTEXT_BODY_TEXT_LARGE, STYLE_SECONDARY); |
| label->SetHorizontalAlignment(gfx::ALIGN_LEFT); |
| return label; |
| } |
| |
| std::unique_ptr<views::Textfield> CreateUsernameEditable( |
| const autofill::PasswordForm& form) { |
| auto editable = std::make_unique<views::Textfield>(); |
| editable->SetText(form.username_value); |
| editable->SetAccessibleName( |
| l10n_util::GetStringUTF16(IDS_PASSWORD_MANAGER_USERNAME_LABEL)); |
| // In case of long username, ensure that the beginning of value is visible. |
| editable->SelectRange(gfx::Range(0)); |
| return editable; |
| } |
| |
| std::unique_ptr<views::Label> CreatePasswordLabel( |
| const autofill::PasswordForm& form, |
| bool is_password_visible) { |
| base::string16 text = |
| form.federation_origin.unique() |
| ? form.password_value |
| : l10n_util::GetStringFUTF16( |
| IDS_PASSWORD_MANAGER_SIGNIN_VIA_FEDERATION, |
| base::UTF8ToUTF16(form.federation_origin.host())); |
| auto label = std::make_unique<views::Label>(text, CONTEXT_BODY_TEXT_LARGE, |
| STYLE_SECONDARY); |
| label->SetHorizontalAlignment(gfx::ALIGN_LEFT); |
| if (form.federation_origin.unique() && !is_password_visible) |
| label->SetObscured(true); |
| if (!form.federation_origin.unique()) |
| label->SetElideBehavior(gfx::ELIDE_HEAD); |
| label->SetFocusBehavior(views::View::FocusBehavior::ACCESSIBLE_ONLY); |
| return label; |
| } |
| |
| // An entry for each credential. Relays delete/undo actions associated with |
| // this password row to parent dialog. |
| class ManagePasswordItemsView::PasswordRow : public views::ButtonListener { |
| public: |
| PasswordRow(ManagePasswordItemsView* parent, |
| const autofill::PasswordForm* password_form); |
| |
| void AddToLayout(views::GridLayout* layout); |
| |
| private: |
| void AddUndoRow(views::GridLayout* layout); |
| void AddPasswordRow(views::GridLayout* layout); |
| |
| // views::ButtonListener: |
| void ButtonPressed(views::Button* sender, const ui::Event& event) override; |
| |
| ManagePasswordItemsView* const parent_; |
| const autofill::PasswordForm* const password_form_; |
| bool deleted_ = false; |
| |
| DISALLOW_COPY_AND_ASSIGN(PasswordRow); |
| }; |
| |
| ManagePasswordItemsView::PasswordRow::PasswordRow( |
| ManagePasswordItemsView* parent, |
| const autofill::PasswordForm* password_form) |
| : parent_(parent), password_form_(password_form) {} |
| |
| void ManagePasswordItemsView::PasswordRow::AddToLayout( |
| views::GridLayout* layout) { |
| if (deleted_) { |
| AddUndoRow(layout); |
| } else { |
| AddPasswordRow(layout); |
| } |
| } |
| |
| void ManagePasswordItemsView::PasswordRow::AddUndoRow( |
| views::GridLayout* layout) { |
| std::unique_ptr<views::Label> text = std::make_unique<views::Label>( |
| l10n_util::GetStringUTF16(IDS_MANAGE_PASSWORDS_DELETED), |
| CONTEXT_BODY_TEXT_LARGE); |
| text->SetHorizontalAlignment(gfx::ALIGN_LEFT); |
| std::unique_ptr<views::LabelButton> undo_button = |
| CreateUndoButton(this, GetDisplayUsername(*password_form_)); |
| |
| StartRow(layout, UNDO_COLUMN_SET); |
| layout->AddView(text.release()); |
| layout->AddView(undo_button.release()); |
| } |
| |
| void ManagePasswordItemsView::PasswordRow::AddPasswordRow( |
| views::GridLayout* layout) { |
| std::unique_ptr<views::Label> username_label = |
| CreateUsernameLabel(*password_form_); |
| std::unique_ptr<views::Label> password_label = |
| CreatePasswordLabel(*password_form_, false); |
| std::unique_ptr<views::ImageButton> delete_button = |
| CreateDeleteButton(this, GetDisplayUsername(*password_form_)); |
| StartRow(layout, PASSWORD_COLUMN_SET); |
| layout->AddView(username_label.release()); |
| layout->AddView(password_label.release()); |
| layout->AddView(delete_button.release()); |
| } |
| |
| void ManagePasswordItemsView::PasswordRow::ButtonPressed( |
| views::Button* sender, |
| const ui::Event& event) { |
| DCHECK(sender->tag() == kDeleteButtonTag || sender->tag() == kUndoButtonTag); |
| deleted_ = sender->tag() == kDeleteButtonTag; |
| parent_->NotifyPasswordFormAction( |
| *password_form_, deleted_ ? ManagePasswordsBubbleModel::REMOVE_PASSWORD |
| : ManagePasswordsBubbleModel::ADD_PASSWORD); |
| } |
| |
| ManagePasswordItemsView::ManagePasswordItemsView( |
| content::WebContents* web_contents, |
| views::View* anchor_view, |
| const gfx::Point& anchor_point, |
| DisplayReason reason) |
| : ManagePasswordsBubbleDelegateViewBase(web_contents, |
| anchor_view, |
| anchor_point, |
| reason) { |
| DCHECK_EQ(password_manager::ui::MANAGE_STATE, model()->state()); |
| |
| if (model()->local_credentials().empty()) { |
| views::LayoutManager* layout = new views::FillLayout(); |
| SetLayoutManager(layout); |
| views::Label* no_passwords_label = new views::Label( |
| l10n_util::GetStringUTF16(IDS_MANAGE_PASSWORDS_NO_PASSWORDS), |
| CONTEXT_BODY_TEXT_SMALL); |
| no_passwords_label->SetMultiLine(true); |
| no_passwords_label->SetHorizontalAlignment(gfx::ALIGN_LEFT); |
| AddChildView(no_passwords_label); |
| } else { |
| for (auto& password_form : model()->local_credentials()) { |
| password_rows_.push_back( |
| std::make_unique<PasswordRow>(this, &password_form)); |
| } |
| |
| RecreateLayout(); |
| } |
| } |
| |
| ManagePasswordItemsView::~ManagePasswordItemsView() = default; |
| |
| void ManagePasswordItemsView::RecreateLayout() { |
| // This method should only be used when we have password rows, otherwise the |
| // dialog should only show the empty label which doesn't need to be recreated. |
| DCHECK(!model()->local_credentials().empty()); |
| |
| RemoveAllChildViews(true); |
| |
| views::GridLayout* grid_layout = views::GridLayout::CreateAndInstall(this); |
| |
| const int vertical_padding = ChromeLayoutProvider::Get()->GetDistanceMetric( |
| DISTANCE_CONTROL_LIST_VERTICAL); |
| bool first_row = true; |
| for (auto& row : password_rows_) { |
| if (!first_row) |
| grid_layout->AddPaddingRow(0, vertical_padding); |
| |
| row->AddToLayout(grid_layout); |
| first_row = false; |
| } |
| |
| PreferredSizeChanged(); |
| if (GetBubbleFrameView()) |
| SizeToContents(); |
| } |
| |
| void ManagePasswordItemsView::NotifyPasswordFormAction( |
| const autofill::PasswordForm& password_form, |
| ManagePasswordsBubbleModel::PasswordAction action) { |
| RecreateLayout(); |
| // After the view is consistent, notify the model that the password needs to |
| // be updated (either removed or put back into the store, as appropriate. |
| model()->OnPasswordAction(password_form, action); |
| } |
| |
| views::View* ManagePasswordItemsView::CreateExtraView() { |
| return views::MdTextButton::CreateSecondaryUiButton( |
| this, l10n_util::GetStringUTF16(IDS_MANAGE_PASSWORDS_BUBBLE_LINK)); |
| } |
| |
| int ManagePasswordItemsView::GetDialogButtons() const { |
| return ui::DIALOG_BUTTON_OK; |
| } |
| |
| bool ManagePasswordItemsView::ShouldShowCloseButton() const { |
| return true; |
| } |
| |
| gfx::Size ManagePasswordItemsView::CalculatePreferredSize() const { |
| const int width = ChromeLayoutProvider::Get()->GetDistanceMetric( |
| DISTANCE_BUBBLE_PREFERRED_WIDTH) - |
| margins().width(); |
| return gfx::Size(width, GetHeightForWidth(width)); |
| } |
| |
| void ManagePasswordItemsView::ButtonPressed(views::Button* sender, |
| const ui::Event& event) { |
| model()->OnManageClicked(); |
| CloseBubble(); |
| } |