bookmark_bubble_view.cc revision 0f1bc08d4cfcc34181b0b5cbf065c40f687bf740
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 // |parent_combobox_| needs to be destroyed before |parent_model_| as it 96 // uses |parent_model_| in its destructor. 97 delete parent_combobox_; 98} 99 100views::View* BookmarkBubbleView::GetInitiallyFocusedView() { 101 return title_tf_; 102} 103 104void BookmarkBubbleView::WindowClosing() { 105 // We have to reset |bubble_| here, not in our destructor, because we'll be 106 // destroyed asynchronously and the shown state will be checked before then. 107 DCHECK_EQ(bookmark_bubble_, this); 108 bookmark_bubble_ = NULL; 109 110 if (observer_) 111 observer_->OnBookmarkBubbleHidden(); 112} 113 114bool BookmarkBubbleView::AcceleratorPressed( 115 const ui::Accelerator& accelerator) { 116 if (accelerator.key_code() == ui::VKEY_RETURN) { 117 if (edit_button_->HasFocus()) 118 HandleButtonPressed(edit_button_); 119 else 120 HandleButtonPressed(close_button_); 121 return true; 122 } else if (accelerator.key_code() == ui::VKEY_ESCAPE) { 123 remove_bookmark_ = newly_bookmarked_; 124 apply_edits_ = false; 125 } 126 127 return BubbleDelegateView::AcceleratorPressed(accelerator); 128} 129 130void BookmarkBubbleView::Init() { 131 views::Label* title_label = new views::Label( 132 l10n_util::GetStringUTF16( 133 newly_bookmarked_ ? IDS_BOOKMARK_BUBBLE_PAGE_BOOKMARKED : 134 IDS_BOOKMARK_BUBBLE_PAGE_BOOKMARK)); 135 ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance(); 136 title_label->SetFont(rb->GetFont(ui::ResourceBundle::MediumFont)); 137 138 remove_button_ = new views::LabelButton(this, l10n_util::GetStringUTF16( 139 IDS_BOOKMARK_BUBBLE_REMOVE_BOOKMARK)); 140 remove_button_->SetStyle(views::Button::STYLE_NATIVE_TEXTBUTTON); 141 142 edit_button_ = new views::LabelButton( 143 this, l10n_util::GetStringUTF16(IDS_BOOKMARK_BUBBLE_OPTIONS)); 144 edit_button_->SetStyle(views::Button::STYLE_NATIVE_TEXTBUTTON); 145 146 close_button_ = new views::LabelButton( 147 this, l10n_util::GetStringUTF16(IDS_DONE)); 148 close_button_->SetStyle(views::Button::STYLE_NATIVE_TEXTBUTTON); 149 close_button_->SetIsDefault(true); 150 151 views::Label* combobox_label = new views::Label( 152 l10n_util::GetStringUTF16(IDS_BOOKMARK_BUBBLE_FOLDER_TEXT)); 153 154 parent_combobox_ = new views::Combobox(&parent_model_); 155 parent_combobox_->set_listener(this); 156 parent_combobox_->SetAccessibleName(combobox_label->text()); 157 158 GridLayout* layout = new GridLayout(this); 159 SetLayoutManager(layout); 160 161 // Column sets used in the layout of the bubble. 162 enum ColumnSetID { 163 TITLE_COLUMN_SET_ID, 164 CONTENT_COLUMN_SET_ID, 165 SYNC_PROMO_COLUMN_SET_ID 166 }; 167 168 ColumnSet* cs = layout->AddColumnSet(TITLE_COLUMN_SET_ID); 169 cs->AddPaddingColumn(0, views::kButtonHEdgeMarginNew); 170 cs->AddColumn(GridLayout::CENTER, GridLayout::CENTER, 0, GridLayout::USE_PREF, 171 0, 0); 172 cs->AddPaddingColumn(0, views::kButtonHEdgeMarginNew); 173 174 // The column layout used for middle and bottom rows. 175 cs = layout->AddColumnSet(CONTENT_COLUMN_SET_ID); 176 cs->AddPaddingColumn(0, views::kButtonHEdgeMarginNew); 177 cs->AddColumn(GridLayout::LEADING, GridLayout::CENTER, 0, 178 GridLayout::USE_PREF, 0, 0); 179 cs->AddPaddingColumn(0, views::kUnrelatedControlHorizontalSpacing); 180 181 cs->AddColumn(GridLayout::FILL, GridLayout::CENTER, 0, 182 GridLayout::USE_PREF, 0, 0); 183 cs->AddPaddingColumn(1, views::kUnrelatedControlLargeHorizontalSpacing); 184 185 cs->AddColumn(GridLayout::LEADING, GridLayout::TRAILING, 0, 186 GridLayout::USE_PREF, 0, 0); 187 cs->AddPaddingColumn(0, views::kRelatedButtonHSpacing); 188 cs->AddColumn(GridLayout::LEADING, GridLayout::TRAILING, 0, 189 GridLayout::USE_PREF, 0, 0); 190 cs->AddPaddingColumn(0, views::kButtonHEdgeMarginNew); 191 192 layout->StartRow(0, TITLE_COLUMN_SET_ID); 193 layout->AddView(title_label); 194 layout->AddPaddingRow(0, views::kUnrelatedControlHorizontalSpacing); 195 196 layout->StartRow(0, CONTENT_COLUMN_SET_ID); 197 views::Label* label = new views::Label( 198 l10n_util::GetStringUTF16(IDS_BOOKMARK_BUBBLE_TITLE_TEXT)); 199 layout->AddView(label); 200 title_tf_ = new views::Textfield(); 201 title_tf_->SetText(GetTitle()); 202 layout->AddView(title_tf_, 5, 1); 203 204 layout->AddPaddingRow(0, views::kUnrelatedControlHorizontalSpacing); 205 206 layout->StartRow(0, CONTENT_COLUMN_SET_ID); 207 layout->AddView(combobox_label); 208 layout->AddView(parent_combobox_, 5, 1); 209 210 layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing); 211 212 layout->StartRow(0, CONTENT_COLUMN_SET_ID); 213 layout->SkipColumns(2); 214 layout->AddView(remove_button_); 215 layout->AddView(edit_button_); 216 layout->AddView(close_button_); 217 218 layout->AddPaddingRow( 219 0, 220 views::kUnrelatedControlVerticalSpacing - kControlBorderWidth); 221 222 if (SyncPromoUI::ShouldShowSyncPromo(profile_)) { 223 // The column layout used for the sync promo. 224 cs = layout->AddColumnSet(SYNC_PROMO_COLUMN_SET_ID); 225 cs->AddColumn(GridLayout::FILL, 226 GridLayout::FILL, 227 1, 228 GridLayout::USE_PREF, 229 0, 230 0); 231 layout->StartRow(0, SYNC_PROMO_COLUMN_SET_ID); 232 233 sync_promo_view_ = new BookmarkSyncPromoView(delegate_.get()); 234 layout->AddView(sync_promo_view_); 235 } 236 237 AddAccelerator(ui::Accelerator(ui::VKEY_RETURN, ui::EF_NONE)); 238} 239 240BookmarkBubbleView::BookmarkBubbleView( 241 views::View* anchor_view, 242 BookmarkBubbleViewObserver* observer, 243 scoped_ptr<BookmarkBubbleDelegate> delegate, 244 Profile* profile, 245 const GURL& url, 246 bool newly_bookmarked) 247 : BubbleDelegateView(anchor_view, views::BubbleBorder::TOP_RIGHT), 248 observer_(observer), 249 delegate_(delegate.Pass()), 250 profile_(profile), 251 url_(url), 252 newly_bookmarked_(newly_bookmarked), 253 parent_model_( 254 BookmarkModelFactory::GetForProfile(profile_), 255 BookmarkModelFactory::GetForProfile(profile_)-> 256 GetMostRecentlyAddedNodeForURL(url)), 257 remove_button_(NULL), 258 edit_button_(NULL), 259 close_button_(NULL), 260 title_tf_(NULL), 261 parent_combobox_(NULL), 262 sync_promo_view_(NULL), 263 remove_bookmark_(false), 264 apply_edits_(true) { 265 const SkColor background_color = GetNativeTheme()->GetSystemColor( 266 ui::NativeTheme::kColorId_DialogBackground); 267 set_color(background_color); 268 set_background(views::Background::CreateSolidBackground(background_color)); 269 set_margins(gfx::Insets(views::kPanelVertMargin, 0, 0, 0)); 270 // Compensate for built-in vertical padding in the anchor view's image. 271 set_anchor_view_insets(gfx::Insets(2, 0, 2, 0)); 272} 273 274string16 BookmarkBubbleView::GetTitle() { 275 BookmarkModel* bookmark_model = 276 BookmarkModelFactory::GetForProfile(profile_); 277 const BookmarkNode* node = 278 bookmark_model->GetMostRecentlyAddedNodeForURL(url_); 279 if (node) 280 return node->GetTitle(); 281 else 282 NOTREACHED(); 283 return string16(); 284} 285 286gfx::Size BookmarkBubbleView::GetMinimumSize() { 287 gfx::Size size(views::BubbleDelegateView::GetPreferredSize()); 288 size.SetToMax(gfx::Size(kMinBubbleWidth, 0)); 289 return size; 290} 291 292void BookmarkBubbleView::ButtonPressed(views::Button* sender, 293 const ui::Event& event) { 294 HandleButtonPressed(sender); 295} 296 297void BookmarkBubbleView::OnSelectedIndexChanged(views::Combobox* combobox) { 298 if (combobox->selected_index() + 1 == parent_model_.GetItemCount()) { 299 content::RecordAction(UserMetricsAction("BookmarkBubble_EditFromCombobox")); 300 ShowEditor(); 301 } 302} 303 304void BookmarkBubbleView::HandleButtonPressed(views::Button* sender) { 305 if (sender == remove_button_) { 306 content::RecordAction(UserMetricsAction("BookmarkBubble_Unstar")); 307 // Set this so we remove the bookmark after the window closes. 308 remove_bookmark_ = true; 309 apply_edits_ = false; 310 StartFade(false); 311 } else if (sender == edit_button_) { 312 content::RecordAction(UserMetricsAction("BookmarkBubble_Edit")); 313 ShowEditor(); 314 } else { 315 DCHECK_EQ(close_button_, sender); 316 StartFade(false); 317 } 318} 319 320void BookmarkBubbleView::ShowEditor() { 321 const BookmarkNode* node = BookmarkModelFactory::GetForProfile( 322 profile_)->GetMostRecentlyAddedNodeForURL(url_); 323 views::Widget* parent = anchor_widget(); 324 DCHECK(parent); 325 326 Profile* profile = profile_; 327 ApplyEdits(); 328 GetWidget()->Close(); 329 330 if (node && parent) 331 BookmarkEditor::Show(parent->GetNativeWindow(), profile, 332 BookmarkEditor::EditDetails::EditNode(node), 333 BookmarkEditor::SHOW_TREE); 334} 335 336void BookmarkBubbleView::ApplyEdits() { 337 // Set this to make sure we don't attempt to apply edits again. 338 apply_edits_ = false; 339 340 BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile_); 341 const BookmarkNode* node = model->GetMostRecentlyAddedNodeForURL(url_); 342 if (node) { 343 const string16 new_title = title_tf_->text(); 344 if (new_title != node->GetTitle()) { 345 model->SetTitle(node, new_title); 346 content::RecordAction( 347 UserMetricsAction("BookmarkBubble_ChangeTitleInBubble")); 348 } 349 parent_model_.MaybeChangeParent(node, parent_combobox_->selected_index()); 350 } 351} 352