bookmark_menu_delegate.cc revision 868fa2fe829687343ffae624259930155e16dbd8
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_menu_delegate.h" 6 7#include "base/prefs/pref_service.h" 8#include "base/strings/utf_string_conversions.h" 9#include "chrome/browser/bookmarks/bookmark_model.h" 10#include "chrome/browser/bookmarks/bookmark_model_factory.h" 11#include "chrome/browser/bookmarks/bookmark_node_data.h" 12#include "chrome/browser/bookmarks/bookmark_utils.h" 13#include "chrome/browser/profiles/profile.h" 14#include "chrome/browser/ui/bookmarks/bookmark_drag_drop.h" 15#include "chrome/browser/ui/bookmarks/bookmark_utils.h" 16#include "chrome/browser/ui/browser.h" 17#include "chrome/browser/ui/views/bookmarks/bookmark_bar_view.h" 18#include "chrome/browser/ui/views/bookmarks/bookmark_drag_drop_views.h" 19#include "chrome/browser/ui/views/event_utils.h" 20#include "chrome/common/pref_names.h" 21#include "content/public/browser/page_navigator.h" 22#include "content/public/browser/user_metrics.h" 23#include "grit/generated_resources.h" 24#include "grit/theme_resources.h" 25#include "grit/ui_resources.h" 26#include "ui/base/dragdrop/os_exchange_data.h" 27#include "ui/base/l10n/l10n_util.h" 28#include "ui/base/resource/resource_bundle.h" 29#include "ui/base/window_open_disposition.h" 30#include "ui/views/controls/button/menu_button.h" 31#include "ui/views/controls/menu/menu_item_view.h" 32#include "ui/views/controls/menu/submenu_view.h" 33#include "ui/views/widget/widget.h" 34 35using content::PageNavigator; 36using content::UserMetricsAction; 37using views::MenuItemView; 38 39// Max width of a menu. There does not appear to be an OS value for this, yet 40// both IE and FF restrict the max width of a menu. 41static const int kMaxMenuWidth = 400; 42 43BookmarkMenuDelegate::BookmarkMenuDelegate(Browser* browser, 44 PageNavigator* navigator, 45 views::Widget* parent, 46 int first_menu_id) 47 : browser_(browser), 48 profile_(browser->profile()), 49 page_navigator_(navigator), 50 parent_(parent), 51 menu_(NULL), 52 for_drop_(false), 53 parent_menu_item_(NULL), 54 next_menu_id_(first_menu_id), 55 real_delegate_(NULL), 56 is_mutating_model_(false), 57 location_(bookmark_utils::LAUNCH_NONE){ 58} 59 60BookmarkMenuDelegate::~BookmarkMenuDelegate() { 61 BookmarkModelFactory::GetForProfile(profile_)->RemoveObserver(this); 62} 63 64void BookmarkMenuDelegate::Init( 65 views::MenuDelegate* real_delegate, 66 MenuItemView* parent, 67 const BookmarkNode* node, 68 int start_child_index, 69 ShowOptions show_options, 70 bookmark_utils::BookmarkLaunchLocation location) { 71 BookmarkModelFactory::GetForProfile(profile_)->AddObserver(this); 72 real_delegate_ = real_delegate; 73 if (parent) { 74 parent_menu_item_ = parent; 75 int initial_count = parent->GetSubmenu() ? 76 parent->GetSubmenu()->GetMenuItemCount() : 0; 77 if ((start_child_index < node->child_count()) && 78 (initial_count > 0)) { 79 parent->AppendSeparator(); 80 } 81 BuildMenu(node, start_child_index, parent, &next_menu_id_); 82 if (show_options == SHOW_PERMANENT_FOLDERS) 83 BuildMenusForPermanentNodes(parent, &next_menu_id_); 84 } else { 85 menu_ = CreateMenu(node, start_child_index, show_options); 86 } 87 88 location_ = location; 89} 90 91void BookmarkMenuDelegate::SetPageNavigator(PageNavigator* navigator) { 92 page_navigator_ = navigator; 93 if (context_menu_.get()) 94 context_menu_->SetPageNavigator(navigator); 95} 96 97void BookmarkMenuDelegate::SetActiveMenu(const BookmarkNode* node, 98 int start_index) { 99 DCHECK(!parent_menu_item_); 100 if (!node_to_menu_map_[node]) 101 CreateMenu(node, start_index, HIDE_PERMANENT_FOLDERS); 102 menu_ = node_to_menu_map_[node]; 103} 104 105string16 BookmarkMenuDelegate::GetTooltipText( 106 int id, 107 const gfx::Point& screen_loc) const { 108 MenuIDToNodeMap::const_iterator i = menu_id_to_node_map_.find(id); 109 DCHECK(i != menu_id_to_node_map_.end()); 110 const BookmarkNode* node = i->second; 111 if (node->is_url()) { 112 return BookmarkBarView::CreateToolTipForURLAndTitle( 113 screen_loc, node->url(), node->GetTitle(), profile_, 114 parent()->GetNativeView()); 115 } 116 return string16(); 117} 118 119bool BookmarkMenuDelegate::IsTriggerableEvent(views::MenuItemView* menu, 120 const ui::Event& e) { 121 return e.type() == ui::ET_GESTURE_TAP || 122 e.type() == ui::ET_GESTURE_TAP_DOWN || 123 event_utils::IsPossibleDispositionEvent(e); 124} 125 126void BookmarkMenuDelegate::ExecuteCommand(int id, int mouse_event_flags) { 127 DCHECK(menu_id_to_node_map_.find(id) != menu_id_to_node_map_.end()); 128 129 const BookmarkNode* node = menu_id_to_node_map_[id]; 130 std::vector<const BookmarkNode*> selection; 131 selection.push_back(node); 132 133 chrome::OpenAll(parent_->GetNativeWindow(), page_navigator_, selection, 134 ui::DispositionFromEventFlags(mouse_event_flags), 135 profile_); 136 bookmark_utils::RecordBookmarkLaunch(location_); 137} 138 139bool BookmarkMenuDelegate::ShouldExecuteCommandWithoutClosingMenu( 140 int id, const ui::Event& event) { 141 return (event.flags() & ui::EF_LEFT_MOUSE_BUTTON) && 142 ui::DispositionFromEventFlags(event.flags()) == NEW_BACKGROUND_TAB; 143} 144 145bool BookmarkMenuDelegate::GetDropFormats( 146 MenuItemView* menu, 147 int* formats, 148 std::set<ui::OSExchangeData::CustomFormat>* custom_formats) { 149 *formats = ui::OSExchangeData::URL; 150 custom_formats->insert(BookmarkNodeData::GetBookmarkCustomFormat()); 151 return true; 152} 153 154bool BookmarkMenuDelegate::AreDropTypesRequired(MenuItemView* menu) { 155 return true; 156} 157 158bool BookmarkMenuDelegate::CanDrop(MenuItemView* menu, 159 const ui::OSExchangeData& data) { 160 // Only accept drops of 1 node, which is the case for all data dragged from 161 // bookmark bar and menus. 162 163 if (!drop_data_.Read(data) || drop_data_.elements.size() != 1 || 164 !profile_->GetPrefs()->GetBoolean(prefs::kEditBookmarksEnabled)) 165 return false; 166 167 if (drop_data_.has_single_url()) 168 return true; 169 170 const BookmarkNode* drag_node = drop_data_.GetFirstNode(profile_); 171 if (!drag_node) { 172 // Dragging a folder from another profile, always accept. 173 return true; 174 } 175 176 // Drag originated from same profile and is not a URL. Only accept it if 177 // the dragged node is not a parent of the node menu represents. 178 if (menu_id_to_node_map_.find(menu->GetCommand()) == 179 menu_id_to_node_map_.end()) { 180 // If we don't know the menu assume its because we're embedded. We'll 181 // figure out the real operation when GetDropOperation is invoked. 182 return true; 183 } 184 const BookmarkNode* drop_node = menu_id_to_node_map_[menu->GetCommand()]; 185 DCHECK(drop_node); 186 while (drop_node && drop_node != drag_node) 187 drop_node = drop_node->parent(); 188 return (drop_node == NULL); 189} 190 191int BookmarkMenuDelegate::GetDropOperation( 192 MenuItemView* item, 193 const ui::DropTargetEvent& event, 194 views::MenuDelegate::DropPosition* position) { 195 // Should only get here if we have drop data. 196 DCHECK(drop_data_.is_valid()); 197 198 const BookmarkNode* node = menu_id_to_node_map_[item->GetCommand()]; 199 const BookmarkNode* drop_parent = node->parent(); 200 int index_to_drop_at = drop_parent->GetIndexOf(node); 201 switch (*position) { 202 case views::MenuDelegate::DROP_AFTER: 203 if (node == BookmarkModelFactory::GetForProfile( 204 profile_)->other_node() || 205 node == BookmarkModelFactory::GetForProfile( 206 profile_)->mobile_node()) { 207 // Dropping after these nodes makes no sense. 208 *position = views::MenuDelegate::DROP_NONE; 209 } 210 index_to_drop_at++; 211 break; 212 213 case views::MenuDelegate::DROP_BEFORE: 214 if (node == BookmarkModelFactory::GetForProfile( 215 profile_)->mobile_node()) { 216 // Dropping before this node makes no sense. 217 *position = views::MenuDelegate::DROP_NONE; 218 } 219 break; 220 221 case views::MenuDelegate::DROP_ON: 222 drop_parent = node; 223 index_to_drop_at = node->child_count(); 224 break; 225 226 default: 227 break; 228 } 229 DCHECK(drop_parent); 230 return chrome::GetBookmarkDropOperation(profile_, event, drop_data_, 231 drop_parent, index_to_drop_at); 232} 233 234int BookmarkMenuDelegate::OnPerformDrop( 235 MenuItemView* menu, 236 views::MenuDelegate::DropPosition position, 237 const ui::DropTargetEvent& event) { 238 const BookmarkNode* drop_node = menu_id_to_node_map_[menu->GetCommand()]; 239 DCHECK(drop_node); 240 BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile_); 241 DCHECK(model); 242 const BookmarkNode* drop_parent = drop_node->parent(); 243 DCHECK(drop_parent); 244 int index_to_drop_at = drop_parent->GetIndexOf(drop_node); 245 switch (position) { 246 case views::MenuDelegate::DROP_AFTER: 247 index_to_drop_at++; 248 break; 249 250 case views::MenuDelegate::DROP_ON: 251 DCHECK(drop_node->is_folder()); 252 drop_parent = drop_node; 253 index_to_drop_at = drop_node->child_count(); 254 break; 255 256 case views::MenuDelegate::DROP_BEFORE: 257 if (drop_node == model->other_node() || 258 drop_node == model->mobile_node()) { 259 // This can happen with SHOW_PERMANENT_FOLDERS. 260 drop_parent = model->bookmark_bar_node(); 261 index_to_drop_at = drop_parent->child_count(); 262 } 263 break; 264 265 default: 266 break; 267 } 268 269 return chrome::DropBookmarks(profile_, drop_data_, 270 drop_parent, index_to_drop_at); 271} 272 273bool BookmarkMenuDelegate::ShowContextMenu(MenuItemView* source, 274 int id, 275 const gfx::Point& p, 276 bool is_mouse_gesture) { 277 DCHECK(menu_id_to_node_map_.find(id) != menu_id_to_node_map_.end()); 278 std::vector<const BookmarkNode*> nodes; 279 nodes.push_back(menu_id_to_node_map_[id]); 280 bool close_on_delete = !parent_menu_item_ && 281 (nodes[0]->parent() == BookmarkModelFactory::GetForProfile( 282 profile())->other_node() && 283 nodes[0]->parent()->child_count() == 1); 284 context_menu_.reset( 285 new BookmarkContextMenu( 286 parent_, 287 browser_, 288 profile_, 289 page_navigator_, 290 nodes[0]->parent(), 291 nodes, 292 close_on_delete)); 293 context_menu_->set_observer(this); 294 context_menu_->RunMenuAt(p); 295 context_menu_.reset(NULL); 296 return true; 297} 298 299bool BookmarkMenuDelegate::CanDrag(MenuItemView* menu) { 300 const BookmarkNode* node = menu_id_to_node_map_[menu->GetCommand()]; 301 // Don't let users drag the other folder. 302 return node->parent() != BookmarkModelFactory::GetForProfile( 303 profile_)->root_node(); 304} 305 306void BookmarkMenuDelegate::WriteDragData(MenuItemView* sender, 307 ui::OSExchangeData* data) { 308 DCHECK(sender && data); 309 310 content::RecordAction(UserMetricsAction("BookmarkBar_DragFromFolder")); 311 312 BookmarkNodeData drag_data(menu_id_to_node_map_[sender->GetCommand()]); 313 drag_data.Write(profile_, data); 314} 315 316int BookmarkMenuDelegate::GetDragOperations(MenuItemView* sender) { 317 return chrome::GetBookmarkDragOperation( 318 profile_, menu_id_to_node_map_[sender->GetCommand()]); 319} 320 321int BookmarkMenuDelegate::GetMaxWidthForMenu(MenuItemView* menu) { 322 return kMaxMenuWidth; 323} 324 325void BookmarkMenuDelegate::BookmarkModelChanged() { 326} 327 328void BookmarkMenuDelegate::BookmarkNodeFaviconChanged( 329 BookmarkModel* model, 330 const BookmarkNode* node) { 331 NodeToMenuMap::iterator menu_pair = node_to_menu_map_.find(node); 332 if (menu_pair == node_to_menu_map_.end()) 333 return; // We're not showing a menu item for the node. 334 335 menu_pair->second->SetIcon(model->GetFavicon(node).AsImageSkia()); 336} 337 338void BookmarkMenuDelegate::WillRemoveBookmarks( 339 const std::vector<const BookmarkNode*>& bookmarks) { 340 DCHECK(!is_mutating_model_); 341 is_mutating_model_ = true; // Set to false in DidRemoveBookmarks(). 342 343 // Remove the observer so that when the remove happens we don't prematurely 344 // cancel the menu. The observer is added back in DidRemoveBookmarks(). 345 BookmarkModelFactory::GetForProfile(profile_)->RemoveObserver(this); 346 347 // Remove the menu items. 348 std::set<MenuItemView*> changed_parent_menus; 349 for (std::vector<const BookmarkNode*>::const_iterator i(bookmarks.begin()); 350 i != bookmarks.end(); ++i) { 351 NodeToMenuMap::iterator node_to_menu = node_to_menu_map_.find(*i); 352 if (node_to_menu != node_to_menu_map_.end()) { 353 MenuItemView* menu = node_to_menu->second; 354 MenuItemView* parent = menu->GetParentMenuItem(); 355 // |parent| is NULL when removing a root. This happens when right clicking 356 // to delete an empty folder. 357 if (parent) { 358 changed_parent_menus.insert(parent); 359 parent->RemoveMenuItemAt(menu->parent()->GetIndexOf(menu)); 360 } 361 node_to_menu_map_.erase(node_to_menu); 362 menu_id_to_node_map_.erase(menu->GetCommand()); 363 } 364 } 365 366 // All the bookmarks in |bookmarks| should have the same parent. It's possible 367 // to support different parents, but this would need to prune any nodes whose 368 // parent has been removed. As all nodes currently have the same parent, there 369 // is the DCHECK. 370 DCHECK(changed_parent_menus.size() <= 1); 371 372 for (std::set<MenuItemView*>::const_iterator i(changed_parent_menus.begin()); 373 i != changed_parent_menus.end(); ++i) 374 (*i)->ChildrenChanged(); 375 376 // Remove any descendants of the removed nodes in |node_to_menu_map_|. 377 for (NodeToMenuMap::iterator i(node_to_menu_map_.begin()); 378 i != node_to_menu_map_.end(); ) { 379 bool ancestor_removed = false; 380 for (std::vector<const BookmarkNode*>::const_iterator j(bookmarks.begin()); 381 j != bookmarks.end(); ++j) { 382 if (i->first->HasAncestor(*j)) { 383 ancestor_removed = true; 384 break; 385 } 386 } 387 if (ancestor_removed) { 388 menu_id_to_node_map_.erase(i->second->GetCommand()); 389 node_to_menu_map_.erase(i++); 390 } else { 391 ++i; 392 } 393 } 394} 395 396void BookmarkMenuDelegate::DidRemoveBookmarks() { 397 // Balances remove in WillRemoveBookmarksImpl. 398 BookmarkModelFactory::GetForProfile(profile_)->AddObserver(this); 399 DCHECK(is_mutating_model_); 400 is_mutating_model_ = false; 401} 402 403MenuItemView* BookmarkMenuDelegate::CreateMenu(const BookmarkNode* parent, 404 int start_child_index, 405 ShowOptions show_options) { 406 MenuItemView* menu = new MenuItemView(real_delegate_); 407 menu->SetCommand(next_menu_id_++); 408 menu_id_to_node_map_[menu->GetCommand()] = parent; 409 menu->set_has_icons(true); 410 BuildMenu(parent, start_child_index, menu, &next_menu_id_); 411 if (show_options == SHOW_PERMANENT_FOLDERS) 412 BuildMenusForPermanentNodes(menu, &next_menu_id_); 413 return menu; 414} 415 416void BookmarkMenuDelegate::BuildMenusForPermanentNodes( 417 views::MenuItemView* menu, 418 int* next_menu_id) { 419 BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile_); 420 bool added_separator = false; 421 BuildMenuForPermanentNode(model->other_node(), menu, next_menu_id, 422 &added_separator); 423 BuildMenuForPermanentNode(model->mobile_node(), menu, next_menu_id, 424 &added_separator); 425} 426 427void BookmarkMenuDelegate::BuildMenuForPermanentNode( 428 const BookmarkNode* node, 429 MenuItemView* menu, 430 int* next_menu_id, 431 bool* added_separator) { 432 if (!node->IsVisible() || node->GetTotalNodeCount() == 1) 433 return; // No children, don't create a menu. 434 435 if (!*added_separator) { 436 *added_separator = true; 437 menu->AppendSeparator(); 438 } 439 int id = *next_menu_id; 440 (*next_menu_id)++; 441 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); 442 gfx::ImageSkia* folder_icon = rb.GetImageSkiaNamed(IDR_BOOKMARK_BAR_FOLDER); 443 MenuItemView* submenu = menu->AppendSubMenuWithIcon( 444 id, node->GetTitle(), *folder_icon); 445 BuildMenu(node, 0, submenu, next_menu_id); 446 menu_id_to_node_map_[id] = node; 447} 448 449void BookmarkMenuDelegate::BuildMenu(const BookmarkNode* parent, 450 int start_child_index, 451 MenuItemView* menu, 452 int* next_menu_id) { 453 node_to_menu_map_[parent] = menu; 454 DCHECK(parent->empty() || start_child_index < parent->child_count()); 455 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); 456 for (int i = start_child_index; i < parent->child_count(); ++i) { 457 const BookmarkNode* node = parent->GetChild(i); 458 const int id = *next_menu_id; 459 (*next_menu_id)++; 460 461 menu_id_to_node_map_[id] = node; 462 if (node->is_url()) { 463 const gfx::Image& image = BookmarkModelFactory::GetForProfile( 464 profile_)->GetFavicon(node); 465 const gfx::ImageSkia* icon = image.IsEmpty() ? 466 rb.GetImageSkiaNamed(IDR_DEFAULT_FAVICON) : image.ToImageSkia(); 467 node_to_menu_map_[node] = 468 menu->AppendMenuItemWithIcon(id, node->GetTitle(), *icon); 469 } else if (node->is_folder()) { 470 gfx::ImageSkia* folder_icon = 471 rb.GetImageSkiaNamed(IDR_BOOKMARK_BAR_FOLDER); 472 MenuItemView* submenu = menu->AppendSubMenuWithIcon( 473 id, node->GetTitle(), *folder_icon); 474 BuildMenu(node, 0, submenu, next_menu_id); 475 } else { 476 NOTREACHED(); 477 } 478 } 479} 480