bookmark_bubble_view.cc revision c2e0dbddbe15c98d52c4786dac06cb8952a8ae6d
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/string16.h" 8#include "base/string_util.h" 9#include "base/utf_string_conversions.h" 10#include "chrome/app/chrome_command_ids.h" 11#include "chrome/browser/bookmarks/bookmark_editor.h" 12#include "chrome/browser/bookmarks/bookmark_model.h" 13#include "chrome/browser/bookmarks/bookmark_model_factory.h" 14#include "chrome/browser/bookmarks/bookmark_utils.h" 15#include "chrome/browser/profiles/profile.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/controls/button/label_button.h" 26#include "ui/views/controls/combobox/combobox.h" 27#include "ui/views/controls/label.h" 28#include "ui/views/controls/link.h" 29#include "ui/views/controls/textfield/textfield.h" 30#include "ui/views/layout/grid_layout.h" 31#include "ui/views/layout/layout_constants.h" 32#include "ui/views/widget/widget.h" 33 34using content::UserMetricsAction; 35using views::ColumnSet; 36using views::GridLayout; 37 38namespace { 39 40// Padding between "Title:" and the actual title. 41const int kTitlePadding = 4; 42 43// Minimum width for the fields - they will push out the size of the bubble if 44// necessary. This should be big enough so that the field pushes the right side 45// of the bubble far enough so that the edit button's left edge is to the right 46// of the field's left edge. 47const int kMinimumFieldSize = 180; 48 49} // namespace 50 51// Declared in browser_dialogs.h so callers don't have to depend on our header. 52 53namespace chrome { 54 55void ShowBookmarkBubbleView(views::View* anchor_view, 56 BookmarkBubbleViewObserver* observer, 57 Profile* profile, 58 const GURL& url, 59 bool newly_bookmarked) { 60 BookmarkBubbleView::ShowBubble(anchor_view, observer, profile, url, 61 newly_bookmarked); 62} 63 64void HideBookmarkBubbleView() { 65 BookmarkBubbleView::Hide(); 66} 67 68bool IsBookmarkBubbleViewShowing() { 69 return BookmarkBubbleView::IsShowing(); 70} 71 72} // namespace chrome 73 74// BookmarkBubbleView --------------------------------------------------------- 75 76BookmarkBubbleView* BookmarkBubbleView::bookmark_bubble_ = NULL; 77 78// static 79void BookmarkBubbleView::ShowBubble(views::View* anchor_view, 80 BookmarkBubbleViewObserver* observer, 81 Profile* profile, 82 const GURL& url, 83 bool newly_bookmarked) { 84 if (IsShowing()) 85 return; 86 87 bookmark_bubble_ = new BookmarkBubbleView(anchor_view, observer, profile, url, 88 newly_bookmarked); 89 views::BubbleDelegateView::CreateBubble(bookmark_bubble_)->Show(); 90 // Select the entire title textfield contents when the bubble is first shown. 91 bookmark_bubble_->title_tf_->SelectAll(true); 92 93 if (bookmark_bubble_->observer_) 94 bookmark_bubble_->observer_->OnBookmarkBubbleShown(url); 95} 96 97// static 98bool BookmarkBubbleView::IsShowing() { 99 return bookmark_bubble_ != NULL; 100} 101 102void BookmarkBubbleView::Hide() { 103 if (IsShowing()) 104 bookmark_bubble_->GetWidget()->Close(); 105} 106 107BookmarkBubbleView::~BookmarkBubbleView() { 108 if (apply_edits_) { 109 ApplyEdits(); 110 } else if (remove_bookmark_) { 111 BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile_); 112 const BookmarkNode* node = model->GetMostRecentlyAddedNodeForURL(url_); 113 if (node) 114 model->Remove(node->parent(), node->parent()->GetIndexOf(node)); 115 } 116} 117 118views::View* BookmarkBubbleView::GetInitiallyFocusedView() { 119 return title_tf_; 120} 121 122void BookmarkBubbleView::WindowClosing() { 123 // We have to reset |bubble_| here, not in our destructor, because we'll be 124 // destroyed asynchronously and the shown state will be checked before then. 125 DCHECK_EQ(bookmark_bubble_, this); 126 bookmark_bubble_ = NULL; 127 128 if (observer_) 129 observer_->OnBookmarkBubbleHidden(); 130 } 131 132bool BookmarkBubbleView::AcceleratorPressed( 133 const ui::Accelerator& accelerator) { 134 if (accelerator.key_code() == ui::VKEY_RETURN) { 135 if (edit_button_->HasFocus()) 136 HandleButtonPressed(edit_button_); 137 else 138 HandleButtonPressed(close_button_); 139 return true; 140 } else if (accelerator.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 remove_link_ = new views::Link(l10n_util::GetStringUTF16( 150 IDS_BOOKMARK_BUBBLE_REMOVE_BOOKMARK)); 151 remove_link_->set_listener(this); 152 153 edit_button_ = new views::LabelButton( 154 this, l10n_util::GetStringUTF16(IDS_BOOKMARK_BUBBLE_OPTIONS)); 155 edit_button_->SetStyle(views::Button::STYLE_NATIVE_TEXTBUTTON); 156 157 close_button_ = new views::LabelButton( 158 this, l10n_util::GetStringUTF16(IDS_DONE)); 159 close_button_->SetStyle(views::Button::STYLE_NATIVE_TEXTBUTTON); 160 close_button_->SetIsDefault(true); 161 162 views::Label* combobox_label = new views::Label( 163 l10n_util::GetStringUTF16(IDS_BOOKMARK_BUBBLE_FOLDER_TEXT)); 164 165 parent_combobox_ = new views::Combobox(&parent_model_); 166 parent_combobox_->set_listener(this); 167 parent_combobox_->SetAccessibleName(combobox_label->text()); 168 169 views::Label* title_label = new views::Label( 170 l10n_util::GetStringUTF16( 171 newly_bookmarked_ ? IDS_BOOKMARK_BUBBLE_PAGE_BOOKMARKED : 172 IDS_BOOKMARK_BUBBLE_PAGE_BOOKMARK)); 173 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); 174 title_label->SetFont(rb.GetFont(ui::ResourceBundle::MediumFont)); 175 title_label->SetEnabledColor(SkColorSetRGB(6, 45, 117)); 176 177 GridLayout* layout = new GridLayout(this); 178 SetLayoutManager(layout); 179 180 ColumnSet* cs = layout->AddColumnSet(0); 181 182 // Top (title) row. 183 cs->AddColumn(GridLayout::CENTER, GridLayout::CENTER, 0, GridLayout::USE_PREF, 184 0, 0); 185 cs->AddPaddingColumn(1, views::kUnrelatedControlHorizontalSpacing); 186 cs->AddColumn(GridLayout::CENTER, GridLayout::CENTER, 0, GridLayout::USE_PREF, 187 0, 0); 188 189 // Middle (input field) rows. 190 cs = layout->AddColumnSet(2); 191 cs->AddColumn(GridLayout::LEADING, GridLayout::CENTER, 0, 192 GridLayout::USE_PREF, 0, 0); 193 cs->AddPaddingColumn(0, views::kRelatedControlHorizontalSpacing); 194 cs->AddColumn(GridLayout::FILL, GridLayout::CENTER, 1, 195 GridLayout::USE_PREF, 0, kMinimumFieldSize); 196 197 // Bottom (buttons) row. 198 cs = layout->AddColumnSet(3); 199 cs->AddPaddingColumn(1, views::kRelatedControlHorizontalSpacing); 200 cs->AddColumn(GridLayout::LEADING, GridLayout::TRAILING, 0, 201 GridLayout::USE_PREF, 0, 0); 202 // We subtract 2 to account for the natural button padding, and 203 // to bring the separation visually in line with the row separation 204 // height. 205 cs->AddPaddingColumn(0, views::kRelatedButtonHSpacing - 2); 206 cs->AddColumn(GridLayout::LEADING, GridLayout::TRAILING, 0, 207 GridLayout::USE_PREF, 0, 0); 208 209 layout->StartRow(0, 0); 210 layout->AddView(title_label); 211 layout->AddView(remove_link_); 212 213 layout->AddPaddingRow(0, views::kRelatedControlSmallVerticalSpacing); 214 layout->StartRow(0, 2); 215 views::Label* label = new views::Label( 216 l10n_util::GetStringUTF16(IDS_BOOKMARK_BUBBLE_TITLE_TEXT)); 217 layout->AddView(label); 218 title_tf_ = new views::Textfield(); 219 title_tf_->SetText(GetTitle()); 220 layout->AddView(title_tf_); 221 222 layout->AddPaddingRow(0, views::kRelatedControlSmallVerticalSpacing); 223 224 layout->StartRow(0, 2); 225 layout->AddView(combobox_label); 226 layout->AddView(parent_combobox_); 227 layout->AddPaddingRow(0, views::kRelatedControlSmallVerticalSpacing); 228 229 layout->StartRow(0, 3); 230 layout->AddView(edit_button_); 231 layout->AddView(close_button_); 232 233 AddAccelerator(ui::Accelerator(ui::VKEY_RETURN, ui::EF_NONE)); 234} 235 236BookmarkBubbleView::BookmarkBubbleView(views::View* anchor_view, 237 BookmarkBubbleViewObserver* observer, 238 Profile* profile, 239 const GURL& url, 240 bool newly_bookmarked) 241 : BubbleDelegateView(anchor_view, views::BubbleBorder::TOP_RIGHT), 242 observer_(observer), 243 profile_(profile), 244 url_(url), 245 newly_bookmarked_(newly_bookmarked), 246 parent_model_( 247 BookmarkModelFactory::GetForProfile(profile_), 248 BookmarkModelFactory::GetForProfile(profile_)-> 249 GetMostRecentlyAddedNodeForURL(url)), 250 remove_link_(NULL), 251 edit_button_(NULL), 252 close_button_(NULL), 253 title_tf_(NULL), 254 parent_combobox_(NULL), 255 remove_bookmark_(false), 256 apply_edits_(true) { 257 // Compensate for built-in vertical padding in the anchor view's image. 258 set_anchor_view_insets(gfx::Insets(5, 0, 5, 0)); 259} 260 261string16 BookmarkBubbleView::GetTitle() { 262 BookmarkModel* bookmark_model = 263 BookmarkModelFactory::GetForProfile(profile_); 264 const BookmarkNode* node = 265 bookmark_model->GetMostRecentlyAddedNodeForURL(url_); 266 if (node) 267 return node->GetTitle(); 268 else 269 NOTREACHED(); 270 return string16(); 271} 272 273void BookmarkBubbleView::ButtonPressed(views::Button* sender, 274 const ui::Event& event) { 275 HandleButtonPressed(sender); 276} 277 278void BookmarkBubbleView::LinkClicked(views::Link* source, int event_flags) { 279 DCHECK_EQ(remove_link_, source); 280 content::RecordAction(UserMetricsAction("BookmarkBubble_Unstar")); 281 282 // Set this so we remove the bookmark after the window closes. 283 remove_bookmark_ = true; 284 apply_edits_ = false; 285 StartFade(false); 286} 287 288void BookmarkBubbleView::OnSelectedIndexChanged(views::Combobox* combobox) { 289 if (combobox->selected_index() + 1 == parent_model_.GetItemCount()) { 290 content::RecordAction(UserMetricsAction("BookmarkBubble_EditFromCombobox")); 291 ShowEditor(); 292 } 293} 294 295void BookmarkBubbleView::HandleButtonPressed(views::Button* sender) { 296 if (sender == edit_button_) { 297 content::RecordAction(UserMetricsAction("BookmarkBubble_Edit")); 298 ShowEditor(); 299 } else { 300 DCHECK_EQ(close_button_, sender); 301 StartFade(false); 302 } 303} 304 305void BookmarkBubbleView::ShowEditor() { 306 const BookmarkNode* node = BookmarkModelFactory::GetForProfile( 307 profile_)->GetMostRecentlyAddedNodeForURL(url_); 308 views::Widget* parent = anchor_widget(); 309 DCHECK(parent); 310 311 Profile* profile = profile_; 312 ApplyEdits(); 313 GetWidget()->Close(); 314 315 if (node && parent) 316 BookmarkEditor::Show(parent->GetNativeWindow(), profile, 317 BookmarkEditor::EditDetails::EditNode(node), 318 BookmarkEditor::SHOW_TREE); 319} 320 321void BookmarkBubbleView::ApplyEdits() { 322 // Set this to make sure we don't attempt to apply edits again. 323 apply_edits_ = false; 324 325 BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile_); 326 const BookmarkNode* node = model->GetMostRecentlyAddedNodeForURL(url_); 327 if (node) { 328 const string16 new_title = title_tf_->text(); 329 if (new_title != node->GetTitle()) { 330 model->SetTitle(node, new_title); 331 content::RecordAction( 332 UserMetricsAction("BookmarkBubble_ChangeTitleInBubble")); 333 } 334 parent_model_.MaybeChangeParent(node, parent_combobox_->selected_index()); 335 } 336} 337