bookmark_bubble_view.cc revision d0247b1b59f9c528cb6df88b4f2b9afaf80d181e
1// Copyright (c) 2012 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/bookmarks/bookmark_bubble_view.h" 6 7#include "base/strings/string16.h" 8#include "base/strings/string_util.h" 9#include "base/strings/utf_string_conversions.h" 10#include "chrome/app/chrome_command_ids.h" 11#include "chrome/browser/bookmarks/bookmark_model.h" 12#include "chrome/browser/bookmarks/bookmark_model_factory.h" 13#include "chrome/browser/bookmarks/bookmark_utils.h" 14#include "chrome/browser/profiles/profile.h" 15#include "chrome/browser/ui/bookmarks/bookmark_editor.h" 16#include "chrome/browser/ui/sync/sync_promo_ui.h" 17#include "chrome/browser/ui/views/bookmarks/bookmark_bubble_view_observer.h" 18#include "chrome/browser/ui/views/bookmarks/bookmark_sync_promo_view.h" 19#include "content/public/browser/user_metrics.h" 20#include "grit/generated_resources.h" 21#include "grit/theme_resources.h" 22#include "ui/base/l10n/l10n_util.h" 23#include "ui/base/resource/resource_bundle.h" 24#include "ui/events/keycodes/keyboard_codes.h" 25#include "ui/views/bubble/bubble_frame_view.h" 26#include "ui/views/controls/button/label_button.h" 27#include "ui/views/controls/combobox/combobox.h" 28#include "ui/views/controls/label.h" 29#include "ui/views/controls/link.h" 30#include "ui/views/controls/textfield/textfield.h" 31#include "ui/views/layout/grid_layout.h" 32#include "ui/views/layout/layout_constants.h" 33#include "ui/views/widget/widget.h" 34 35using content::UserMetricsAction; 36using views::ColumnSet; 37using views::GridLayout; 38 39namespace { 40 41// Minimum width of the the bubble. 42const int kMinBubbleWidth = 350; 43 44// Width of the border of a button. 45const int kControlBorderWidth = 2; 46 47} // namespace 48 49BookmarkBubbleView* BookmarkBubbleView::bookmark_bubble_ = NULL; 50 51// static 52void BookmarkBubbleView::ShowBubble(views::View* anchor_view, 53 BookmarkBubbleViewObserver* observer, 54 scoped_ptr<BookmarkBubbleDelegate> delegate, 55 Profile* profile, 56 const GURL& url, 57 bool newly_bookmarked) { 58 if (IsShowing()) 59 return; 60 61 bookmark_bubble_ = new BookmarkBubbleView(anchor_view, 62 observer, 63 delegate.Pass(), 64 profile, 65 url, 66 newly_bookmarked); 67 views::BubbleDelegateView::CreateBubble(bookmark_bubble_)->Show(); 68 // Select the entire title textfield contents when the bubble is first shown. 69 bookmark_bubble_->title_tf_->SelectAll(true); 70 bookmark_bubble_->SetArrowPaintType(views::BubbleBorder::PAINT_NONE); 71 72 if (bookmark_bubble_->observer_) 73 bookmark_bubble_->observer_->OnBookmarkBubbleShown(url); 74} 75 76// static 77bool BookmarkBubbleView::IsShowing() { 78 return bookmark_bubble_ != NULL; 79} 80 81void BookmarkBubbleView::Hide() { 82 if (IsShowing()) 83 bookmark_bubble_->GetWidget()->Close(); 84} 85 86BookmarkBubbleView::~BookmarkBubbleView() { 87 if (apply_edits_) { 88 ApplyEdits(); 89 } else if (remove_bookmark_) { 90 BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile_); 91 const BookmarkNode* node = model->GetMostRecentlyAddedNodeForURL(url_); 92 if (node) 93 model->Remove(node->parent(), node->parent()->GetIndexOf(node)); 94 } 95} 96 97views::View* BookmarkBubbleView::GetInitiallyFocusedView() { 98 return title_tf_; 99} 100 101void BookmarkBubbleView::WindowClosing() { 102 // We have to reset |bubble_| here, not in our destructor, because we'll be 103 // destroyed asynchronously and the shown state will be checked before then. 104 DCHECK_EQ(bookmark_bubble_, this); 105 bookmark_bubble_ = NULL; 106 107 if (observer_) 108 observer_->OnBookmarkBubbleHidden(); 109} 110 111bool BookmarkBubbleView::AcceleratorPressed( 112 const ui::Accelerator& accelerator) { 113 if (accelerator.key_code() == ui::VKEY_RETURN) { 114 if (edit_button_->HasFocus()) 115 HandleButtonPressed(edit_button_); 116 else 117 HandleButtonPressed(close_button_); 118 return true; 119 } else if (accelerator.key_code() == ui::VKEY_ESCAPE) { 120 remove_bookmark_ = newly_bookmarked_; 121 apply_edits_ = false; 122 } 123 124 return BubbleDelegateView::AcceleratorPressed(accelerator); 125} 126 127void BookmarkBubbleView::Init() { 128 views::Label* title_label = new views::Label( 129 l10n_util::GetStringUTF16( 130 newly_bookmarked_ ? IDS_BOOKMARK_BUBBLE_PAGE_BOOKMARKED : 131 IDS_BOOKMARK_BUBBLE_PAGE_BOOKMARK)); 132 ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance(); 133 title_label->SetFont(rb->GetFont(ui::ResourceBundle::MediumFont)); 134 135 remove_button_ = new views::LabelButton(this, l10n_util::GetStringUTF16( 136 IDS_BOOKMARK_BUBBLE_REMOVE_BOOKMARK)); 137 remove_button_->SetStyle(views::Button::STYLE_NATIVE_TEXTBUTTON); 138 139 edit_button_ = new views::LabelButton( 140 this, l10n_util::GetStringUTF16(IDS_BOOKMARK_BUBBLE_OPTIONS)); 141 edit_button_->SetStyle(views::Button::STYLE_NATIVE_TEXTBUTTON); 142 143 close_button_ = new views::LabelButton( 144 this, l10n_util::GetStringUTF16(IDS_DONE)); 145 close_button_->SetStyle(views::Button::STYLE_NATIVE_TEXTBUTTON); 146 close_button_->SetIsDefault(true); 147 148 views::Label* combobox_label = new views::Label( 149 l10n_util::GetStringUTF16(IDS_BOOKMARK_BUBBLE_FOLDER_TEXT)); 150 151 parent_combobox_ = new views::Combobox(&parent_model_); 152 parent_combobox_->set_listener(this); 153 parent_combobox_->SetAccessibleName(combobox_label->text()); 154 155 GridLayout* layout = new GridLayout(this); 156 SetLayoutManager(layout); 157 158 // Column sets used in the layout of the bubble. 159 enum ColumnSetID { 160 TITLE_COLUMN_SET_ID, 161 CONTENT_COLUMN_SET_ID, 162 SYNC_PROMO_COLUMN_SET_ID 163 }; 164 165 ColumnSet* cs = layout->AddColumnSet(TITLE_COLUMN_SET_ID); 166 cs->AddPaddingColumn(0, views::kButtonHEdgeMarginNew); 167 cs->AddColumn(GridLayout::CENTER, GridLayout::CENTER, 0, GridLayout::USE_PREF, 168 0, 0); 169 cs->AddPaddingColumn(0, views::kButtonHEdgeMarginNew); 170 171 // The column layout used for middle and bottom rows. 172 cs = layout->AddColumnSet(CONTENT_COLUMN_SET_ID); 173 cs->AddPaddingColumn(0, views::kButtonHEdgeMarginNew); 174 cs->AddColumn(GridLayout::LEADING, GridLayout::CENTER, 0, 175 GridLayout::USE_PREF, 0, 0); 176 cs->AddPaddingColumn(0, views::kUnrelatedControlHorizontalSpacing); 177 178 cs->AddColumn(GridLayout::FILL, GridLayout::CENTER, 0, 179 GridLayout::USE_PREF, 0, 0); 180 cs->AddPaddingColumn(1, views::kUnrelatedControlLargeHorizontalSpacing); 181 182 cs->AddColumn(GridLayout::LEADING, GridLayout::TRAILING, 0, 183 GridLayout::USE_PREF, 0, 0); 184 cs->AddPaddingColumn(0, views::kRelatedButtonHSpacing); 185 cs->AddColumn(GridLayout::LEADING, GridLayout::TRAILING, 0, 186 GridLayout::USE_PREF, 0, 0); 187 cs->AddPaddingColumn(0, views::kButtonHEdgeMarginNew); 188 189 layout->StartRow(0, TITLE_COLUMN_SET_ID); 190 layout->AddView(title_label); 191 layout->AddPaddingRow(0, views::kUnrelatedControlHorizontalSpacing); 192 193 layout->StartRow(0, CONTENT_COLUMN_SET_ID); 194 views::Label* label = new views::Label( 195 l10n_util::GetStringUTF16(IDS_BOOKMARK_BUBBLE_TITLE_TEXT)); 196 layout->AddView(label); 197 title_tf_ = new views::Textfield(); 198 title_tf_->SetText(GetTitle()); 199 layout->AddView(title_tf_, 5, 1); 200 201 layout->AddPaddingRow(0, views::kUnrelatedControlHorizontalSpacing); 202 203 layout->StartRow(0, CONTENT_COLUMN_SET_ID); 204 layout->AddView(combobox_label); 205 layout->AddView(parent_combobox_, 5, 1); 206 207 layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing); 208 209 layout->StartRow(0, CONTENT_COLUMN_SET_ID); 210 layout->SkipColumns(2); 211 layout->AddView(remove_button_); 212 layout->AddView(edit_button_); 213 layout->AddView(close_button_); 214 215 layout->AddPaddingRow( 216 0, 217 views::kUnrelatedControlVerticalSpacing - kControlBorderWidth); 218 219 if (SyncPromoUI::ShouldShowSyncPromo(profile_)) { 220 // The column layout used for the sync promo. 221 cs = layout->AddColumnSet(SYNC_PROMO_COLUMN_SET_ID); 222 cs->AddColumn(GridLayout::FILL, 223 GridLayout::FILL, 224 1, 225 GridLayout::USE_PREF, 226 0, 227 0); 228 layout->StartRow(0, SYNC_PROMO_COLUMN_SET_ID); 229 230 sync_promo_view_ = new BookmarkSyncPromoView(delegate_.get()); 231 layout->AddView(sync_promo_view_); 232 } 233 234 AddAccelerator(ui::Accelerator(ui::VKEY_RETURN, ui::EF_NONE)); 235} 236 237BookmarkBubbleView::BookmarkBubbleView( 238 views::View* anchor_view, 239 BookmarkBubbleViewObserver* observer, 240 scoped_ptr<BookmarkBubbleDelegate> delegate, 241 Profile* profile, 242 const GURL& url, 243 bool newly_bookmarked) 244 : BubbleDelegateView(anchor_view, views::BubbleBorder::TOP_RIGHT), 245 observer_(observer), 246 delegate_(delegate.Pass()), 247 profile_(profile), 248 url_(url), 249 newly_bookmarked_(newly_bookmarked), 250 parent_model_( 251 BookmarkModelFactory::GetForProfile(profile_), 252 BookmarkModelFactory::GetForProfile(profile_)-> 253 GetMostRecentlyAddedNodeForURL(url)), 254 remove_button_(NULL), 255 edit_button_(NULL), 256 close_button_(NULL), 257 title_tf_(NULL), 258 parent_combobox_(NULL), 259 sync_promo_view_(NULL), 260 remove_bookmark_(false), 261 apply_edits_(true) { 262 const SkColor background_color = GetNativeTheme()->GetSystemColor( 263 ui::NativeTheme::kColorId_DialogBackground); 264 set_color(background_color); 265 set_background(views::Background::CreateSolidBackground(background_color)); 266 set_margins(gfx::Insets(views::kPanelVertMargin, 0, 0, 0)); 267 // Compensate for built-in vertical padding in the anchor view's image. 268 set_anchor_view_insets(gfx::Insets(7, 0, 7, 0)); 269} 270 271string16 BookmarkBubbleView::GetTitle() { 272 BookmarkModel* bookmark_model = 273 BookmarkModelFactory::GetForProfile(profile_); 274 const BookmarkNode* node = 275 bookmark_model->GetMostRecentlyAddedNodeForURL(url_); 276 if (node) 277 return node->GetTitle(); 278 else 279 NOTREACHED(); 280 return string16(); 281} 282 283gfx::Size BookmarkBubbleView::GetMinimumSize() { 284 gfx::Size size(views::BubbleDelegateView::GetPreferredSize()); 285 size.SetToMax(gfx::Size(kMinBubbleWidth, 0)); 286 return size; 287} 288 289void BookmarkBubbleView::ButtonPressed(views::Button* sender, 290 const ui::Event& event) { 291 HandleButtonPressed(sender); 292} 293 294void BookmarkBubbleView::OnSelectedIndexChanged(views::Combobox* combobox) { 295 if (combobox->selected_index() + 1 == parent_model_.GetItemCount()) { 296 content::RecordAction(UserMetricsAction("BookmarkBubble_EditFromCombobox")); 297 ShowEditor(); 298 } 299} 300 301void BookmarkBubbleView::HandleButtonPressed(views::Button* sender) { 302 if (sender == remove_button_) { 303 content::RecordAction(UserMetricsAction("BookmarkBubble_Unstar")); 304 // Set this so we remove the bookmark after the window closes. 305 remove_bookmark_ = true; 306 apply_edits_ = false; 307 StartFade(false); 308 } else if (sender == edit_button_) { 309 content::RecordAction(UserMetricsAction("BookmarkBubble_Edit")); 310 ShowEditor(); 311 } else { 312 DCHECK_EQ(close_button_, sender); 313 StartFade(false); 314 } 315} 316 317void BookmarkBubbleView::ShowEditor() { 318 const BookmarkNode* node = BookmarkModelFactory::GetForProfile( 319 profile_)->GetMostRecentlyAddedNodeForURL(url_); 320 views::Widget* parent = anchor_widget(); 321 DCHECK(parent); 322 323 Profile* profile = profile_; 324 ApplyEdits(); 325 GetWidget()->Close(); 326 327 if (node && parent) 328 BookmarkEditor::Show(parent->GetNativeWindow(), profile, 329 BookmarkEditor::EditDetails::EditNode(node), 330 BookmarkEditor::SHOW_TREE); 331} 332 333void BookmarkBubbleView::ApplyEdits() { 334 // Set this to make sure we don't attempt to apply edits again. 335 apply_edits_ = false; 336 337 BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile_); 338 const BookmarkNode* node = model->GetMostRecentlyAddedNodeForURL(url_); 339 if (node) { 340 const string16 new_title = title_tf_->text(); 341 if (new_title != node->GetTitle()) { 342 model->SetTitle(node, new_title); 343 content::RecordAction( 344 UserMetricsAction("BookmarkBubble_ChangeTitleInBubble")); 345 } 346 parent_model_.MaybeChangeParent(node, parent_combobox_->selected_index()); 347 } 348} 349