blob: a652f26b0a23170df0752feca5d889e0e093f5ad [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/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.h"
#include "chrome/browser/bookmarks/bookmark_model_factory.h"
#include "chrome/browser/bookmarks/bookmark_utils.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/bookmarks/bookmark_editor.h"
#include "chrome/browser/ui/sync/sync_promo_ui.h"
#include "chrome/browser/ui/views/bookmarks/bookmark_bubble_view_observer.h"
#include "chrome/browser/ui/views/bookmarks/bookmark_sync_promo_view.h"
#include "content/public/browser/user_metrics.h"
#include "grit/generated_resources.h"
#include "grit/theme_resources.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/events/keycodes/keyboard_codes.h"
#include "ui/views/bubble/bubble_frame_view.h"
#include "ui/views/controls/button/label_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 content::UserMetricsAction;
using views::ColumnSet;
using views::GridLayout;
namespace {
// Minimum width of the the bubble.
const int kMinBubbleWidth = 350;
// Width of the border of a button.
const int kControlBorderWidth = 2;
} // namespace
BookmarkBubbleView* BookmarkBubbleView::bookmark_bubble_ = NULL;
// static
void BookmarkBubbleView::ShowBubble(views::View* anchor_view,
BookmarkBubbleViewObserver* observer,
scoped_ptr<BookmarkBubbleDelegate> delegate,
Profile* profile,
const GURL& url,
bool newly_bookmarked) {
if (IsShowing())
return;
bookmark_bubble_ = new BookmarkBubbleView(anchor_view,
observer,
delegate.Pass(),
profile,
url,
newly_bookmarked);
views::BubbleDelegateView::CreateBubble(bookmark_bubble_)->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_NONE);
if (bookmark_bubble_->observer_)
bookmark_bubble_->observer_->OnBookmarkBubbleShown(url);
}
// static
bool BookmarkBubbleView::IsShowing() {
return bookmark_bubble_ != NULL;
}
void BookmarkBubbleView::Hide() {
if (IsShowing())
bookmark_bubble_->GetWidget()->Close();
}
BookmarkBubbleView::~BookmarkBubbleView() {
if (apply_edits_) {
ApplyEdits();
} else if (remove_bookmark_) {
BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile_);
const BookmarkNode* node = model->GetMostRecentlyAddedNodeForURL(url_);
if (node)
model->Remove(node->parent(), node->parent()->GetIndexOf(node));
}
// |parent_combobox_| needs to be destroyed before |parent_model_| as it
// uses |parent_model_| in its destructor.
delete parent_combobox_;
}
views::View* BookmarkBubbleView::GetInitiallyFocusedView() {
return title_tf_;
}
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) {
if (accelerator.key_code() == ui::VKEY_RETURN) {
if (edit_button_->HasFocus())
HandleButtonPressed(edit_button_);
else
HandleButtonPressed(close_button_);
return true;
} else if (accelerator.key_code() == ui::VKEY_ESCAPE) {
remove_bookmark_ = newly_bookmarked_;
apply_edits_ = false;
}
return BubbleDelegateView::AcceleratorPressed(accelerator);
}
void BookmarkBubbleView::Init() {
views::Label* title_label = new views::Label(
l10n_util::GetStringUTF16(
newly_bookmarked_ ? IDS_BOOKMARK_BUBBLE_PAGE_BOOKMARKED :
IDS_BOOKMARK_BUBBLE_PAGE_BOOKMARK));
ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance();
title_label->SetFont(rb->GetFont(ui::ResourceBundle::MediumFont));
remove_button_ = new views::LabelButton(this, l10n_util::GetStringUTF16(
IDS_BOOKMARK_BUBBLE_REMOVE_BOOKMARK));
remove_button_->SetStyle(views::Button::STYLE_NATIVE_TEXTBUTTON);
edit_button_ = new views::LabelButton(
this, l10n_util::GetStringUTF16(IDS_BOOKMARK_BUBBLE_OPTIONS));
edit_button_->SetStyle(views::Button::STYLE_NATIVE_TEXTBUTTON);
close_button_ = new views::LabelButton(
this, l10n_util::GetStringUTF16(IDS_DONE));
close_button_->SetStyle(views::Button::STYLE_NATIVE_TEXTBUTTON);
close_button_->SetIsDefault(true);
views::Label* combobox_label = new views::Label(
l10n_util::GetStringUTF16(IDS_BOOKMARK_BUBBLE_FOLDER_TEXT));
parent_combobox_ = new views::Combobox(&parent_model_);
parent_combobox_->set_listener(this);
parent_combobox_->SetAccessibleName(combobox_label->text());
GridLayout* layout = new GridLayout(this);
SetLayoutManager(layout);
// Column sets used in the layout of the bubble.
enum ColumnSetID {
TITLE_COLUMN_SET_ID,
CONTENT_COLUMN_SET_ID,
SYNC_PROMO_COLUMN_SET_ID
};
ColumnSet* cs = layout->AddColumnSet(TITLE_COLUMN_SET_ID);
cs->AddPaddingColumn(0, views::kButtonHEdgeMarginNew);
cs->AddColumn(GridLayout::CENTER, GridLayout::CENTER, 0, GridLayout::USE_PREF,
0, 0);
cs->AddPaddingColumn(0, views::kButtonHEdgeMarginNew);
// The column layout used for middle and bottom rows.
cs = layout->AddColumnSet(CONTENT_COLUMN_SET_ID);
cs->AddPaddingColumn(0, views::kButtonHEdgeMarginNew);
cs->AddColumn(GridLayout::LEADING, 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);
cs->AddPaddingColumn(0, views::kButtonHEdgeMarginNew);
layout->StartRow(0, TITLE_COLUMN_SET_ID);
layout->AddView(title_label);
layout->AddPaddingRow(0, views::kUnrelatedControlHorizontalSpacing);
layout->StartRow(0, CONTENT_COLUMN_SET_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());
layout->AddView(title_tf_, 5, 1);
layout->AddPaddingRow(0, views::kUnrelatedControlHorizontalSpacing);
layout->StartRow(0, CONTENT_COLUMN_SET_ID);
layout->AddView(combobox_label);
layout->AddView(parent_combobox_, 5, 1);
layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
layout->StartRow(0, CONTENT_COLUMN_SET_ID);
layout->SkipColumns(2);
layout->AddView(remove_button_);
layout->AddView(edit_button_);
layout->AddView(close_button_);
layout->AddPaddingRow(
0,
views::kUnrelatedControlVerticalSpacing - kControlBorderWidth);
if (SyncPromoUI::ShouldShowSyncPromo(profile_)) {
// The column layout used for the sync promo.
cs = layout->AddColumnSet(SYNC_PROMO_COLUMN_SET_ID);
cs->AddColumn(GridLayout::FILL,
GridLayout::FILL,
1,
GridLayout::USE_PREF,
0,
0);
layout->StartRow(0, SYNC_PROMO_COLUMN_SET_ID);
sync_promo_view_ = new BookmarkSyncPromoView(delegate_.get());
layout->AddView(sync_promo_view_);
}
AddAccelerator(ui::Accelerator(ui::VKEY_RETURN, ui::EF_NONE));
}
BookmarkBubbleView::BookmarkBubbleView(
views::View* anchor_view,
BookmarkBubbleViewObserver* observer,
scoped_ptr<BookmarkBubbleDelegate> delegate,
Profile* profile,
const GURL& url,
bool newly_bookmarked)
: BubbleDelegateView(anchor_view, views::BubbleBorder::TOP_RIGHT),
observer_(observer),
delegate_(delegate.Pass()),
profile_(profile),
url_(url),
newly_bookmarked_(newly_bookmarked),
parent_model_(
BookmarkModelFactory::GetForProfile(profile_),
BookmarkModelFactory::GetForProfile(profile_)->
GetMostRecentlyAddedNodeForURL(url)),
remove_button_(NULL),
edit_button_(NULL),
close_button_(NULL),
title_tf_(NULL),
parent_combobox_(NULL),
sync_promo_view_(NULL),
remove_bookmark_(false),
apply_edits_(true) {
const SkColor background_color = GetNativeTheme()->GetSystemColor(
ui::NativeTheme::kColorId_DialogBackground);
set_color(background_color);
set_background(views::Background::CreateSolidBackground(background_color));
set_margins(gfx::Insets(views::kPanelVertMargin, 0, 0, 0));
// Compensate for built-in vertical padding in the anchor view's image.
set_anchor_view_insets(gfx::Insets(2, 0, 2, 0));
}
string16 BookmarkBubbleView::GetTitle() {
BookmarkModel* bookmark_model =
BookmarkModelFactory::GetForProfile(profile_);
const BookmarkNode* node =
bookmark_model->GetMostRecentlyAddedNodeForURL(url_);
if (node)
return node->GetTitle();
else
NOTREACHED();
return string16();
}
gfx::Size BookmarkBubbleView::GetMinimumSize() {
gfx::Size size(views::BubbleDelegateView::GetPreferredSize());
size.SetToMax(gfx::Size(kMinBubbleWidth, 0));
return size;
}
void BookmarkBubbleView::ButtonPressed(views::Button* sender,
const ui::Event& event) {
HandleButtonPressed(sender);
}
void BookmarkBubbleView::OnSelectedIndexChanged(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;
StartFade(false);
} else if (sender == edit_button_) {
content::RecordAction(UserMetricsAction("BookmarkBubble_Edit"));
ShowEditor();
} else {
DCHECK_EQ(close_button_, sender);
StartFade(false);
}
}
void BookmarkBubbleView::ShowEditor() {
const BookmarkNode* node = BookmarkModelFactory::GetForProfile(
profile_)->GetMostRecentlyAddedNodeForURL(url_);
views::Widget* parent = anchor_widget();
DCHECK(parent);
Profile* profile = profile_;
ApplyEdits();
GetWidget()->Close();
if (node && parent)
BookmarkEditor::Show(parent->GetNativeWindow(), 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::GetForProfile(profile_);
const BookmarkNode* node = model->GetMostRecentlyAddedNodeForURL(url_);
if (node) {
const 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());
}
}