blob: ebc0098c576e1a66302ba70e10e4bb7eb8fe56b9 [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 "base/metrics/user_metrics.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "build/buildflag.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/harmony/chrome_layout_provider.h"
#include "chrome/browser/ui/views/harmony/textfield_layout.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 "components/signin/core/browser/profile_management_switches.h"
#include "components/signin/core/browser/signin_features.h"
#include "components/strings/grit/components_strings.h"
#include "ui/accessibility/ax_node_data.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/events/keycodes/keyboard_codes.h"
#include "ui/views/controls/button/md_text_button.h"
#include "ui/views/controls/label.h"
#include "ui/views/controls/textfield/textfield.h"
#include "ui/views/layout/fill_layout.h"
#include "ui/views/layout/grid_layout.h"
#include "ui/views/widget/widget.h"
#if defined(OS_WIN)
#include "chrome/browser/ui/views/desktop_ios_promotion/desktop_ios_promotion_bubble_view.h"
#include "chrome/browser/ui/views/desktop_ios_promotion/desktop_ios_promotion_footnote_view.h"
#endif
#if BUILDFLAG(ENABLE_DICE_SUPPORT)
#include "chrome/browser/signin/account_consistency_mode_manager.h"
#include "chrome/browser/ui/views/sync/dice_bubble_sync_promo_view.h"
#endif
using base::UserMetricsAction;
using bookmarks::BookmarkModel;
using bookmarks::BookmarkNode;
BookmarkBubbleView* BookmarkBubbleView::bookmark_bubble_ = nullptr;
// 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);
// Bookmark bubble should always anchor TOP_RIGHT, but the
// LocationBarBubbleDelegateView does not know that and may use different
// arrow anchoring.
bookmark_bubble_->set_arrow(views::BubbleBorder::TOP_RIGHT);
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_->name_field_->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;
}
// static
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);
}
}
// ui::DialogModel -------------------------------------------------------------
base::string16 BookmarkBubbleView::GetDialogButtonLabel(
ui::DialogButton button) const {
#if defined(OS_WIN)
if (is_showing_ios_promotion_)
return ios_promo_view_->GetDialogButtonLabel(button);
#endif
return l10n_util::GetStringUTF16((button == ui::DIALOG_BUTTON_OK)
? IDS_DONE
: IDS_BOOKMARK_BUBBLE_REMOVE_BOOKMARK);
}
// views::WidgetDelegate -------------------------------------------------------
views::View* BookmarkBubbleView::GetInitiallyFocusedView() {
return name_field_;
}
base::string16 BookmarkBubbleView::GetWindowTitle() const {
#if defined(OS_WIN)
if (is_showing_ios_promotion_) {
return desktop_ios_promotion::GetPromoTitle(
desktop_ios_promotion::PromotionEntryPoint::BOOKMARKS_BUBBLE);
}
#endif
return l10n_util::GetStringUTF16(newly_bookmarked_
? IDS_BOOKMARK_BUBBLE_PAGE_BOOKMARKED
: IDS_BOOKMARK_BUBBLE_PAGE_BOOKMARK);
}
bool BookmarkBubbleView::ShouldShowCloseButton() const {
return true;
}
gfx::ImageSkia BookmarkBubbleView::GetWindowIcon() {
#if defined(OS_WIN)
if (is_showing_ios_promotion_)
return ios_promo_view_->GetWindowIcon();
#endif
return gfx::ImageSkia();
}
bool BookmarkBubbleView::ShouldShowWindowIcon() const {
return is_showing_ios_promotion_;
}
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;
is_showing_ios_promotion_ = false;
if (observer_)
observer_->OnBookmarkBubbleHidden();
}
// views::DialogDelegate -------------------------------------------------------
views::View* BookmarkBubbleView::CreateExtraView() {
edit_button_ = views::MdTextButton::CreateSecondaryUiButton(
this, l10n_util::GetStringUTF16(IDS_BOOKMARK_BUBBLE_OPTIONS));
edit_button_->AddAccelerator(ui::Accelerator(ui::VKEY_E, ui::EF_ALT_DOWN));
return edit_button_;
}
bool BookmarkBubbleView::GetExtraViewPadding(int* padding) {
*padding = ChromeLayoutProvider::Get()->GetDistanceMetric(
DISTANCE_UNRELATED_CONTROL_HORIZONTAL_LARGE);
return true;
}
views::View* BookmarkBubbleView::CreateFootnoteView() {
#if defined(OS_WIN)
if (!is_showing_ios_promotion_ &&
desktop_ios_promotion::IsEligibleForIOSPromotion(
profile_,
desktop_ios_promotion::PromotionEntryPoint::BOOKMARKS_FOOTNOTE)) {
footnote_view_ = new DesktopIOSPromotionFootnoteView(profile_, this);
return footnote_view_;
}
#endif
if (!SyncPromoUI::ShouldShowSyncPromo(profile_))
return nullptr;
base::RecordAction(UserMetricsAction("Signin_Impression_FromBookmarkBubble"));
#if BUILDFLAG(ENABLE_DICE_SUPPORT)
if (AccountConsistencyModeManager::IsDiceEnabledForProfile(profile_)) {
footnote_view_ = new DiceBubbleSyncPromoView(
profile_, delegate_.get(), IDS_BOOKMARK_DICE_PROMO_SIGNIN_MESSAGE,
IDS_BOOKMARK_DICE_PROMO_SYNC_MESSAGE);
} else {
footnote_view_ =
new BubbleSyncPromoView(delegate_.get(), IDS_BOOKMARK_SYNC_PROMO_LINK,
IDS_BOOKMARK_SYNC_PROMO_MESSAGE);
}
#else
footnote_view_ =
new BubbleSyncPromoView(delegate_.get(), IDS_BOOKMARK_SYNC_PROMO_LINK,
IDS_BOOKMARK_SYNC_PROMO_MESSAGE);
#endif
return footnote_view_;
}
bool BookmarkBubbleView::Cancel() {
#if defined(OS_WIN)
if (is_showing_ios_promotion_)
return ios_promo_view_->Cancel();
#endif
base::RecordAction(UserMetricsAction("BookmarkBubble_Unstar"));
// Set this so we remove the bookmark after the window closes.
remove_bookmark_ = true;
apply_edits_ = false;
return true;
}
bool BookmarkBubbleView::Accept() {
#if defined(OS_WIN)
if (is_showing_ios_promotion_)
return ios_promo_view_->Accept();
using desktop_ios_promotion::PromotionEntryPoint;
if (desktop_ios_promotion::IsEligibleForIOSPromotion(
profile_, PromotionEntryPoint::BOOKMARKS_BUBBLE)) {
ShowIOSPromotion(PromotionEntryPoint::BOOKMARKS_BUBBLE);
return false;
}
#endif
return true;
}
bool BookmarkBubbleView::Close() {
// Allow closing when activation lost. Default would call Accept().
return true;
}
void BookmarkBubbleView::UpdateButton(views::LabelButton* button,
ui::DialogButton type) {
LocationBarBubbleDelegateView::UpdateButton(button, type);
if (type == ui::DIALOG_BUTTON_CANCEL)
button->AddAccelerator(ui::Accelerator(ui::VKEY_R, ui::EF_ALT_DOWN));
}
// views::View -----------------------------------------------------------------
const char* BookmarkBubbleView::GetClassName() const {
return "BookmarkBubbleView";
}
void BookmarkBubbleView::GetAccessibleNodeData(ui::AXNodeData* node_data) {
LocationBarBubbleDelegateView::GetAccessibleNodeData(node_data);
node_data->SetName(l10n_util::GetStringUTF8(
newly_bookmarked_ ? IDS_BOOKMARK_BUBBLE_PAGE_BOOKMARKED
: IDS_BOOKMARK_AX_BUBBLE_PAGE_BOOKMARK));
}
// views::ButtonListener -------------------------------------------------------
void BookmarkBubbleView::ButtonPressed(views::Button* sender,
const ui::Event& event) {
base::RecordAction(UserMetricsAction("BookmarkBubble_Edit"));
ShowEditor();
}
// views::ComboboxListener -----------------------------------------------------
void BookmarkBubbleView::OnPerformAction(views::Combobox* combobox) {
if (combobox->selected_index() + 1 == folder_model()->GetItemCount()) {
base::RecordAction(UserMetricsAction("BookmarkBubble_EditFromCombobox"));
ShowEditor();
}
}
// DesktopIOSPromotionFootnoteDelegate -----------------------------------------
void BookmarkBubbleView::OnIOSPromotionFootnoteLinkClicked() {
#if defined(OS_WIN)
ShowIOSPromotion(
desktop_ios_promotion::PromotionEntryPoint::FOOTNOTE_FOLLOWUP_BUBBLE);
#endif
}
// views::BubbleDialogDelegateView ---------------------------------------------
void BookmarkBubbleView::Init() {
using views::GridLayout;
SetLayoutManager(std::make_unique<views::FillLayout>());
bookmark_contents_view_ = new views::View();
GridLayout* layout = bookmark_contents_view_->SetLayoutManager(
std::make_unique<views::GridLayout>(bookmark_contents_view_));
constexpr int kColumnId = 0;
ConfigureTextfieldStack(layout, kColumnId);
name_field_ = AddFirstTextfieldRow(
layout, l10n_util::GetStringUTF16(IDS_BOOKMARK_BUBBLE_NAME_LABEL),
kColumnId);
name_field_->SetText(GetBookmarkName());
name_field_->SetAccessibleName(
l10n_util::GetStringUTF16(IDS_BOOKMARK_AX_BUBBLE_NAME_LABEL));
BookmarkModel* model = BookmarkModelFactory::GetForBrowserContext(profile_);
auto parent_folder_model = std::make_unique<RecentlyUsedFoldersComboModel>(
model, model->GetMostRecentlyAddedUserNodeForURL(url_));
parent_combobox_ = AddComboboxRow(
layout, l10n_util::GetStringUTF16(IDS_BOOKMARK_BUBBLE_FOLDER_LABEL),
std::move(parent_folder_model), kColumnId);
parent_combobox_->set_listener(this);
parent_combobox_->SetAccessibleName(
l10n_util::GetStringUTF16(IDS_BOOKMARK_AX_BUBBLE_FOLDER_LABEL));
AddChildView(bookmark_contents_view_);
}
// Private methods -------------------------------------------------------------
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, gfx::Point(), nullptr),
observer_(observer),
delegate_(std::move(delegate)),
profile_(profile),
url_(url),
newly_bookmarked_(newly_bookmarked) {
chrome::RecordDialogCreation(chrome::DialogIdentifier::BOOKMARK);
set_margins(ChromeLayoutProvider::Get()->GetDialogInsetsForContentType(
views::CONTROL, views::CONTROL));
}
base::string16 BookmarkBubbleView::GetBookmarkName() {
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::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 = name_field_->text();
if (new_title != node->GetTitle()) {
model->SetTitle(node, new_title);
base::RecordAction(
UserMetricsAction("BookmarkBubble_ChangeTitleInBubble"));
}
folder_model()->MaybeChangeParent(node, parent_combobox_->selected_index());
}
}
#if defined(OS_WIN)
void BookmarkBubbleView::ShowIOSPromotion(
desktop_ios_promotion::PromotionEntryPoint entry_point) {
DCHECK(!is_showing_ios_promotion_);
edit_button_->SetVisible(false);
// Hide the contents, but don't delete. Its child views are accessed in the
// destructor if there are edits to apply.
bookmark_contents_view_->SetVisible(false);
delete footnote_view_;
footnote_view_ = nullptr;
is_showing_ios_promotion_ = true;
ios_promo_view_ = new DesktopIOSPromotionBubbleView(profile_, entry_point);
AddChildView(ios_promo_view_);
GetWidget()->UpdateWindowIcon();
GetWidget()->UpdateWindowTitle();
DialogModelChanged();
SizeToContents();
}
#endif