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