blob: ec906adf0a8602974fff1b0a779e2bff1b516809 [file] [log] [blame]
// Copyright (c) 2012 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/bookmarks/bookmark_bubble_view.h"
#include <utility>
#include "base/macros.h"
#include "base/strings/string16.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/app/chrome_command_ids.h"
#include "chrome/browser/bookmarks/bookmark_model_factory.h"
#include "chrome/browser/platform_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/bookmarks/bookmark_bubble_observer.h"
#include "chrome/browser/ui/bookmarks/bookmark_editor.h"
#include "chrome/browser/ui/browser_dialogs.h"
#include "chrome/browser/ui/sync/sync_promo_ui.h"
#include "chrome/browser/ui/views/sync/bubble_sync_promo_view.h"
#include "chrome/grit/chromium_strings.h"
#include "chrome/grit/generated_resources.h"
#include "components/bookmarks/browser/bookmark_model.h"
#include "components/bookmarks/browser/bookmark_utils.h"
#include "content/public/browser/user_metrics.h"
#include "grit/components_strings.h"
#include "ui/accessibility/ax_view_state.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/events/keycodes/keyboard_codes.h"
#include "ui/views/bubble/bubble_frame_view.h"
#include "ui/views/controls/button/md_text_button.h"
#include "ui/views/controls/combobox/combobox.h"
#include "ui/views/controls/label.h"
#include "ui/views/controls/link.h"
#include "ui/views/controls/textfield/textfield.h"
#include "ui/views/layout/grid_layout.h"
#include "ui/views/layout/layout_constants.h"
#include "ui/views/widget/widget.h"
using base::UserMetricsAction;
using bookmarks::BookmarkModel;
using bookmarks::BookmarkNode;
using views::ColumnSet;
using views::GridLayout;
namespace {
// This combobox prevents any lengthy content from stretching the bubble view.
class UnsizedCombobox : public views::Combobox {
public:
explicit UnsizedCombobox(ui::ComboboxModel* model) : views::Combobox(model) {}
~UnsizedCombobox() override {}
gfx::Size GetPreferredSize() const override {
return gfx::Size(0, views::Combobox::GetPreferredSize().height());
}
private:
DISALLOW_COPY_AND_ASSIGN(UnsizedCombobox);
};
} // namespace
BookmarkBubbleView* BookmarkBubbleView::bookmark_bubble_ = NULL;
// static
views::Widget* BookmarkBubbleView::ShowBubble(
views::View* anchor_view,
const gfx::Rect& anchor_rect,
gfx::NativeView parent_window,
bookmarks::BookmarkBubbleObserver* observer,
std::unique_ptr<BubbleSyncPromoDelegate> delegate,
Profile* profile,
const GURL& url,
bool already_bookmarked) {
if (bookmark_bubble_)
return nullptr;
bookmark_bubble_ =
new BookmarkBubbleView(anchor_view, observer, std::move(delegate),
profile, url, !already_bookmarked);
if (!anchor_view) {
bookmark_bubble_->SetAnchorRect(anchor_rect);
bookmark_bubble_->set_parent_window(parent_window);
}
views::Widget* bubble_widget =
views::BubbleDialogDelegateView::CreateBubble(bookmark_bubble_);
bubble_widget->Show();
// Select the entire title textfield contents when the bubble is first shown.
bookmark_bubble_->title_tf_->SelectAll(true);
bookmark_bubble_->SetArrowPaintType(views::BubbleBorder::PAINT_TRANSPARENT);
if (bookmark_bubble_->observer_) {
BookmarkModel* model = BookmarkModelFactory::GetForBrowserContext(profile);
const BookmarkNode* node = model->GetMostRecentlyAddedUserNodeForURL(url);
bookmark_bubble_->observer_->OnBookmarkBubbleShown(node);
}
return bubble_widget;
}
void BookmarkBubbleView::Hide() {
if (bookmark_bubble_)
bookmark_bubble_->GetWidget()->Close();
}
BookmarkBubbleView::~BookmarkBubbleView() {
if (apply_edits_) {
ApplyEdits();
} else if (remove_bookmark_) {
BookmarkModel* model = BookmarkModelFactory::GetForBrowserContext(profile_);
const BookmarkNode* node = model->GetMostRecentlyAddedUserNodeForURL(url_);
if (node)
model->Remove(node);
}
// |parent_combobox_| needs to be destroyed before |parent_model_| as it
// uses |parent_model_| in its destructor.
delete parent_combobox_;
}
void BookmarkBubbleView::WindowClosing() {
// We have to reset |bubble_| here, not in our destructor, because we'll be
// destroyed asynchronously and the shown state will be checked before then.
DCHECK_EQ(bookmark_bubble_, this);
bookmark_bubble_ = NULL;
if (observer_)
observer_->OnBookmarkBubbleHidden();
}
bool BookmarkBubbleView::AcceleratorPressed(
const ui::Accelerator& accelerator) {
ui::KeyboardCode key_code = accelerator.key_code();
if (key_code == ui::VKEY_RETURN) {
HandleButtonPressed(close_button_);
return true;
}
if (key_code == ui::VKEY_E && accelerator.IsAltDown()) {
HandleButtonPressed(edit_button_);
return true;
}
if (key_code == ui::VKEY_R && accelerator.IsAltDown()) {
HandleButtonPressed(remove_button_);
return true;
}
return LocationBarBubbleDelegateView::AcceleratorPressed(accelerator);
}
void BookmarkBubbleView::Init() {
remove_button_ = views::MdTextButton::CreateSecondaryUiButton(
this, l10n_util::GetStringUTF16(IDS_BOOKMARK_BUBBLE_REMOVE_BOOKMARK));
edit_button_ = views::MdTextButton::CreateSecondaryUiButton(
this, l10n_util::GetStringUTF16(IDS_BOOKMARK_BUBBLE_OPTIONS));
close_button_ = views::MdTextButton::CreateSecondaryUiButton(
this, l10n_util::GetStringUTF16(IDS_DONE));
close_button_->SetIsDefault(true);
views::Label* combobox_label = new views::Label(
l10n_util::GetStringUTF16(IDS_BOOKMARK_BUBBLE_FOLDER_TEXT));
parent_combobox_ = new UnsizedCombobox(&parent_model_);
parent_combobox_->set_listener(this);
parent_combobox_->SetAccessibleName(
l10n_util::GetStringUTF16(IDS_BOOKMARK_AX_BUBBLE_FOLDER_TEXT));
GridLayout* layout = new GridLayout(this);
SetLayoutManager(layout);
// This column set is used for the labels and textfields as well as the
// buttons at the bottom.
const int cs_id = 0;
ColumnSet* cs = layout->AddColumnSet(cs_id);
cs->AddColumn(views::kControlLabelGridAlignment, GridLayout::CENTER, 0,
GridLayout::USE_PREF, 0, 0);
cs->AddPaddingColumn(0, views::kUnrelatedControlHorizontalSpacing);
cs->AddColumn(GridLayout::FILL, GridLayout::CENTER, 0,
GridLayout::USE_PREF, 0, 0);
cs->AddPaddingColumn(1, views::kUnrelatedControlLargeHorizontalSpacing);
cs->AddColumn(GridLayout::LEADING, GridLayout::TRAILING, 0,
GridLayout::USE_PREF, 0, 0);
cs->AddPaddingColumn(0, views::kRelatedButtonHSpacing);
cs->AddColumn(GridLayout::LEADING, GridLayout::TRAILING, 0,
GridLayout::USE_PREF, 0, 0);
layout->StartRow(0, cs_id);
views::Label* label = new views::Label(
l10n_util::GetStringUTF16(IDS_BOOKMARK_BUBBLE_TITLE_TEXT));
layout->AddView(label);
title_tf_ = new views::Textfield();
title_tf_->SetText(GetTitle());
title_tf_->SetAccessibleName(
l10n_util::GetStringUTF16(IDS_BOOKMARK_AX_BUBBLE_TITLE_TEXT));
layout->AddView(title_tf_, 5, 1);
layout->AddPaddingRow(0, views::kUnrelatedControlHorizontalSpacing);
layout->StartRow(0, cs_id);
layout->AddView(combobox_label);
layout->AddView(parent_combobox_, 5, 1);
layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
layout->StartRow(0, cs_id);
layout->SkipColumns(2);
layout->AddView(remove_button_);
layout->AddView(edit_button_);
layout->AddView(close_button_);
AddAccelerator(ui::Accelerator(ui::VKEY_RETURN, ui::EF_NONE));
AddAccelerator(ui::Accelerator(ui::VKEY_E, ui::EF_ALT_DOWN));
AddAccelerator(ui::Accelerator(ui::VKEY_R, ui::EF_ALT_DOWN));
}
base::string16 BookmarkBubbleView::GetWindowTitle() const {
return l10n_util::GetStringUTF16(newly_bookmarked_
? IDS_BOOKMARK_BUBBLE_PAGE_BOOKMARKED
: IDS_BOOKMARK_BUBBLE_PAGE_BOOKMARK);
}
const char* BookmarkBubbleView::GetClassName() const {
return "BookmarkBubbleView";
}
views::View* BookmarkBubbleView::GetInitiallyFocusedView() {
return title_tf_;
}
views::View* BookmarkBubbleView::CreateFootnoteView() {
if (!SyncPromoUI::ShouldShowSyncPromo(profile_))
return nullptr;
content::RecordAction(
base::UserMetricsAction("Signin_Impression_FromBookmarkBubble"));
return new BubbleSyncPromoView(delegate_.get(), IDS_BOOKMARK_SYNC_PROMO_LINK,
IDS_BOOKMARK_SYNC_PROMO_MESSAGE);
}
BookmarkBubbleView::BookmarkBubbleView(
views::View* anchor_view,
bookmarks::BookmarkBubbleObserver* observer,
std::unique_ptr<BubbleSyncPromoDelegate> delegate,
Profile* profile,
const GURL& url,
bool newly_bookmarked)
: LocationBarBubbleDelegateView(anchor_view, nullptr),
observer_(observer),
delegate_(std::move(delegate)),
profile_(profile),
url_(url),
newly_bookmarked_(newly_bookmarked),
parent_model_(BookmarkModelFactory::GetForBrowserContext(profile_),
BookmarkModelFactory::GetForBrowserContext(profile_)
->GetMostRecentlyAddedUserNodeForURL(url)),
remove_button_(nullptr),
edit_button_(nullptr),
close_button_(nullptr),
title_tf_(nullptr),
parent_combobox_(nullptr),
remove_bookmark_(false),
apply_edits_(true) {}
base::string16 BookmarkBubbleView::GetTitle() {
BookmarkModel* bookmark_model =
BookmarkModelFactory::GetForBrowserContext(profile_);
const BookmarkNode* node =
bookmark_model->GetMostRecentlyAddedUserNodeForURL(url_);
if (node)
return node->GetTitle();
else
NOTREACHED();
return base::string16();
}
void BookmarkBubbleView::GetAccessibleState(ui::AXViewState* state) {
LocationBarBubbleDelegateView::GetAccessibleState(state);
state->name =
l10n_util::GetStringUTF16(
newly_bookmarked_ ? IDS_BOOKMARK_BUBBLE_PAGE_BOOKMARKED :
IDS_BOOKMARK_AX_BUBBLE_PAGE_BOOKMARK);
}
void BookmarkBubbleView::ButtonPressed(views::Button* sender,
const ui::Event& event) {
HandleButtonPressed(sender);
}
void BookmarkBubbleView::OnPerformAction(views::Combobox* combobox) {
if (combobox->selected_index() + 1 == parent_model_.GetItemCount()) {
content::RecordAction(UserMetricsAction("BookmarkBubble_EditFromCombobox"));
ShowEditor();
}
}
void BookmarkBubbleView::HandleButtonPressed(views::Button* sender) {
if (sender == remove_button_) {
content::RecordAction(UserMetricsAction("BookmarkBubble_Unstar"));
// Set this so we remove the bookmark after the window closes.
remove_bookmark_ = true;
apply_edits_ = false;
GetWidget()->Close();
} else if (sender == edit_button_) {
content::RecordAction(UserMetricsAction("BookmarkBubble_Edit"));
ShowEditor();
} else {
DCHECK_EQ(close_button_, sender);
GetWidget()->Close();
}
}
void BookmarkBubbleView::ShowEditor() {
const BookmarkNode* node =
BookmarkModelFactory::GetForBrowserContext(profile_)
->GetMostRecentlyAddedUserNodeForURL(url_);
gfx::NativeWindow native_parent =
anchor_widget() ? anchor_widget()->GetNativeWindow()
: platform_util::GetTopLevel(parent_window());
DCHECK(native_parent);
Profile* profile = profile_;
ApplyEdits();
GetWidget()->Close();
if (node && native_parent)
BookmarkEditor::Show(native_parent, profile,
BookmarkEditor::EditDetails::EditNode(node),
BookmarkEditor::SHOW_TREE);
}
void BookmarkBubbleView::ApplyEdits() {
// Set this to make sure we don't attempt to apply edits again.
apply_edits_ = false;
BookmarkModel* model = BookmarkModelFactory::GetForBrowserContext(profile_);
const BookmarkNode* node = model->GetMostRecentlyAddedUserNodeForURL(url_);
if (node) {
const base::string16 new_title = title_tf_->text();
if (new_title != node->GetTitle()) {
model->SetTitle(node, new_title);
content::RecordAction(
UserMetricsAction("BookmarkBubble_ChangeTitleInBubble"));
}
parent_model_.MaybeChangeParent(node, parent_combobox_->selected_index());
}
}