bookmark_bubble_view.cc revision 5d1f7b1de12d16ceb2c938c56701a3e8bfa558f7
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/accessibility/accessible_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->GetMostRecentlyAddedNodeForURL(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 GetMostRecentlyAddedNodeForURL(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 const SkColor background_color = GetNativeTheme()->GetSystemColor( 271 ui::NativeTheme::kColorId_DialogBackground); 272 set_color(background_color); 273 set_move_with_anchor(true); 274 set_background(views::Background::CreateSolidBackground(background_color)); 275 set_margins(gfx::Insets(views::kPanelVertMargin, 0, 0, 0)); 276 // Compensate for built-in vertical padding in the anchor view's image. 277 set_anchor_view_insets(gfx::Insets(2, 0, 2, 0)); 278} 279 280base::string16 BookmarkBubbleView::GetTitle() { 281 BookmarkModel* bookmark_model = 282 BookmarkModelFactory::GetForProfile(profile_); 283 const BookmarkNode* node = 284 bookmark_model->GetMostRecentlyAddedNodeForURL(url_); 285 if (node) 286 return node->GetTitle(); 287 else 288 NOTREACHED(); 289 return base::string16(); 290} 291 292gfx::Size BookmarkBubbleView::GetMinimumSize() { 293 gfx::Size size(views::BubbleDelegateView::GetPreferredSize()); 294 size.SetToMax(gfx::Size(kMinBubbleWidth, 0)); 295 return size; 296} 297 298void BookmarkBubbleView::GetAccessibleState(ui::AccessibleViewState* state) { 299 BubbleDelegateView::GetAccessibleState(state); 300 state->name = 301 l10n_util::GetStringUTF16( 302 newly_bookmarked_ ? IDS_BOOKMARK_BUBBLE_PAGE_BOOKMARKED : 303 IDS_BOOKMARK_AX_BUBBLE_PAGE_BOOKMARK); 304} 305 306void BookmarkBubbleView::ButtonPressed(views::Button* sender, 307 const ui::Event& event) { 308 HandleButtonPressed(sender); 309} 310 311void BookmarkBubbleView::OnPerformAction(views::Combobox* combobox) { 312 if (combobox->selected_index() + 1 == parent_model_.GetItemCount()) { 313 content::RecordAction(UserMetricsAction("BookmarkBubble_EditFromCombobox")); 314 ShowEditor(); 315 } 316} 317 318void BookmarkBubbleView::HandleButtonPressed(views::Button* sender) { 319 if (sender == remove_button_) { 320 content::RecordAction(UserMetricsAction("BookmarkBubble_Unstar")); 321 // Set this so we remove the bookmark after the window closes. 322 remove_bookmark_ = true; 323 apply_edits_ = false; 324 StartFade(false); 325 } else if (sender == edit_button_) { 326 content::RecordAction(UserMetricsAction("BookmarkBubble_Edit")); 327 ShowEditor(); 328 } else { 329 DCHECK_EQ(close_button_, sender); 330 StartFade(false); 331 } 332} 333 334void BookmarkBubbleView::ShowEditor() { 335 const BookmarkNode* node = BookmarkModelFactory::GetForProfile( 336 profile_)->GetMostRecentlyAddedNodeForURL(url_); 337 views::Widget* parent = anchor_widget(); 338 DCHECK(parent); 339 340 Profile* profile = profile_; 341 ApplyEdits(); 342 GetWidget()->Close(); 343 344 if (node && parent) 345 BookmarkEditor::Show(parent->GetNativeWindow(), profile, 346 BookmarkEditor::EditDetails::EditNode(node), 347 BookmarkEditor::SHOW_TREE); 348} 349 350void BookmarkBubbleView::ApplyEdits() { 351 // Set this to make sure we don't attempt to apply edits again. 352 apply_edits_ = false; 353 354 BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile_); 355 const BookmarkNode* node = model->GetMostRecentlyAddedNodeForURL(url_); 356 if (node) { 357 const base::string16 new_title = title_tf_->text(); 358 if (new_title != node->GetTitle()) { 359 model->SetTitle(node, new_title); 360 content::RecordAction( 361 UserMetricsAction("BookmarkBubble_ChangeTitleInBubble")); 362 } 363 parent_model_.MaybeChangeParent(node, parent_combobox_->selected_index()); 364 } 365} 366