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