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