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