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