1 // Copyright (c) 2011 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_controller_views.h" 6 7 #include "base/stl_util-inl.h" 8 #include "base/utf_string_conversions.h" 9 #include "chrome/browser/bookmarks/bookmark_model.h" 10 #include "chrome/browser/bookmarks/bookmark_node_data.h" 11 #include "chrome/browser/bookmarks/bookmark_utils.h" 12 #include "chrome/browser/metrics/user_metrics.h" 13 #include "chrome/browser/prefs/pref_service.h" 14 #include "chrome/browser/profiles/profile.h" 15 #include "chrome/browser/ui/browser.h" 16 #include "chrome/browser/ui/views/bookmarks/bookmark_bar_view.h" 17 #include "chrome/browser/ui/views/event_utils.h" 18 #include "chrome/common/pref_names.h" 19 #include "content/browser/tab_contents/page_navigator.h" 20 #include "content/common/page_transition_types.h" 21 #include "grit/app_resources.h" 22 #include "grit/generated_resources.h" 23 #include "grit/theme_resources.h" 24 #include "ui/base/dragdrop/os_exchange_data.h" 25 #include "ui/base/resource/resource_bundle.h" 26 #include "views/controls/button/menu_button.h" 27 28 using views::MenuItemView; 29 30 // Max width of a menu. There does not appear to be an OS value for this, yet 31 // both IE and FF restrict the max width of a menu. 32 static const int kMaxMenuWidth = 400; 33 34 BookmarkMenuController::BookmarkMenuController(Browser* browser, 35 Profile* profile, 36 PageNavigator* navigator, 37 gfx::NativeWindow parent, 38 const BookmarkNode* node, 39 int start_child_index) 40 : browser_(browser), 41 profile_(profile), 42 page_navigator_(navigator), 43 parent_(parent), 44 node_(node), 45 menu_(NULL), 46 observer_(NULL), 47 for_drop_(false), 48 bookmark_bar_(NULL), 49 next_menu_id_(1) { 50 menu_ = CreateMenu(node, start_child_index); 51 } 52 53 void BookmarkMenuController::RunMenuAt(BookmarkBarView* bookmark_bar, 54 bool for_drop) { 55 bookmark_bar_ = bookmark_bar; 56 views::MenuButton* menu_button = bookmark_bar_->GetMenuButtonForNode(node_); 57 DCHECK(menu_button); 58 MenuItemView::AnchorPosition anchor; 59 int start_index; 60 bookmark_bar_->GetAnchorPositionAndStartIndexForButton( 61 menu_button, &anchor, &start_index); 62 RunMenuAt(menu_button, anchor, for_drop); 63 } 64 65 void BookmarkMenuController::RunMenuAt( 66 views::MenuButton* button, 67 MenuItemView::AnchorPosition position, 68 bool for_drop) { 69 gfx::Point screen_loc; 70 views::View::ConvertPointToScreen(button, &screen_loc); 71 // Subtract 1 from the height to make the popup flush with the button border. 72 gfx::Rect bounds(screen_loc.x(), screen_loc.y(), button->width(), 73 button->height() - 1); 74 for_drop_ = for_drop; 75 profile_->GetBookmarkModel()->AddObserver(this); 76 // The constructor creates the initial menu and that is all we should have 77 // at this point. 78 DCHECK(node_to_menu_map_.size() == 1); 79 if (for_drop) { 80 menu_->RunMenuForDropAt(parent_, bounds, position); 81 } else { 82 menu_->RunMenuAt(parent_, button, bounds, position, false); 83 delete this; 84 } 85 } 86 87 void BookmarkMenuController::Cancel() { 88 menu_->Cancel(); 89 } 90 91 std::wstring BookmarkMenuController::GetTooltipText( 92 int id, const gfx::Point& screen_loc) { 93 DCHECK(menu_id_to_node_map_.find(id) != menu_id_to_node_map_.end()); 94 95 const BookmarkNode* node = menu_id_to_node_map_[id]; 96 if (node->type() == BookmarkNode::URL) 97 return BookmarkBarView::CreateToolTipForURLAndTitle( 98 screen_loc, node->GetURL(), UTF16ToWide(node->GetTitle()), profile_); 99 return std::wstring(); 100 } 101 102 bool BookmarkMenuController::IsTriggerableEvent(const views::MouseEvent& e) { 103 return event_utils::IsPossibleDispositionEvent(e); 104 } 105 106 void BookmarkMenuController::ExecuteCommand(int id, int mouse_event_flags) { 107 DCHECK(page_navigator_); 108 DCHECK(menu_id_to_node_map_.find(id) != menu_id_to_node_map_.end()); 109 110 const BookmarkNode* node = menu_id_to_node_map_[id]; 111 std::vector<const BookmarkNode*> selection; 112 selection.push_back(node); 113 114 WindowOpenDisposition initial_disposition = 115 event_utils::DispositionFromEventFlags(mouse_event_flags); 116 117 bookmark_utils::OpenAll(parent_, profile_, page_navigator_, selection, 118 initial_disposition); 119 } 120 121 bool BookmarkMenuController::GetDropFormats( 122 MenuItemView* menu, 123 int* formats, 124 std::set<ui::OSExchangeData::CustomFormat>* custom_formats) { 125 *formats = ui::OSExchangeData::URL; 126 custom_formats->insert(BookmarkNodeData::GetBookmarkCustomFormat()); 127 return true; 128 } 129 130 bool BookmarkMenuController::AreDropTypesRequired(MenuItemView* menu) { 131 return true; 132 } 133 134 bool BookmarkMenuController::CanDrop(MenuItemView* menu, 135 const ui::OSExchangeData& data) { 136 // Only accept drops of 1 node, which is the case for all data dragged from 137 // bookmark bar and menus. 138 139 if (!drop_data_.Read(data) || drop_data_.elements.size() != 1 || 140 !profile_->GetPrefs()->GetBoolean(prefs::kEditBookmarksEnabled)) 141 return false; 142 143 if (drop_data_.has_single_url()) 144 return true; 145 146 const BookmarkNode* drag_node = drop_data_.GetFirstNode(profile_); 147 if (!drag_node) { 148 // Dragging a folder from another profile, always accept. 149 return true; 150 } 151 152 // Drag originated from same profile and is not a URL. Only accept it if 153 // the dragged node is not a parent of the node menu represents. 154 const BookmarkNode* drop_node = menu_id_to_node_map_[menu->GetCommand()]; 155 DCHECK(drop_node); 156 while (drop_node && drop_node != drag_node) 157 drop_node = drop_node->parent(); 158 return (drop_node == NULL); 159 } 160 161 int BookmarkMenuController::GetDropOperation( 162 MenuItemView* item, 163 const views::DropTargetEvent& event, 164 DropPosition* position) { 165 // Should only get here if we have drop data. 166 DCHECK(drop_data_.is_valid()); 167 168 const BookmarkNode* node = menu_id_to_node_map_[item->GetCommand()]; 169 const BookmarkNode* drop_parent = node->parent(); 170 int index_to_drop_at = drop_parent->GetIndexOf(node); 171 if (*position == DROP_AFTER) { 172 index_to_drop_at++; 173 } else if (*position == DROP_ON) { 174 drop_parent = node; 175 index_to_drop_at = node->child_count(); 176 } 177 DCHECK(drop_parent); 178 return bookmark_utils::BookmarkDropOperation( 179 profile_, event, drop_data_, drop_parent, index_to_drop_at); 180 } 181 182 int BookmarkMenuController::OnPerformDrop(MenuItemView* menu, 183 DropPosition position, 184 const views::DropTargetEvent& event) { 185 const BookmarkNode* drop_node = menu_id_to_node_map_[menu->GetCommand()]; 186 DCHECK(drop_node); 187 BookmarkModel* model = profile_->GetBookmarkModel(); 188 DCHECK(model); 189 const BookmarkNode* drop_parent = drop_node->parent(); 190 DCHECK(drop_parent); 191 int index_to_drop_at = drop_parent->GetIndexOf(drop_node); 192 if (position == DROP_AFTER) { 193 index_to_drop_at++; 194 } else if (position == DROP_ON) { 195 DCHECK(drop_node->is_folder()); 196 drop_parent = drop_node; 197 index_to_drop_at = drop_node->child_count(); 198 } 199 200 int result = bookmark_utils::PerformBookmarkDrop( 201 profile_, drop_data_, drop_parent, index_to_drop_at); 202 if (for_drop_) 203 delete this; 204 return result; 205 } 206 207 bool BookmarkMenuController::ShowContextMenu(MenuItemView* source, 208 int id, 209 const gfx::Point& p, 210 bool is_mouse_gesture) { 211 DCHECK(menu_id_to_node_map_.find(id) != menu_id_to_node_map_.end()); 212 std::vector<const BookmarkNode*> nodes; 213 nodes.push_back(menu_id_to_node_map_[id]); 214 context_menu_.reset( 215 new BookmarkContextMenu( 216 parent_, 217 profile_, 218 page_navigator_, 219 nodes[0]->parent(), 220 nodes)); 221 context_menu_->set_observer(this); 222 context_menu_->RunMenuAt(p); 223 context_menu_.reset(NULL); 224 return true; 225 } 226 227 void BookmarkMenuController::DropMenuClosed(MenuItemView* menu) { 228 delete this; 229 } 230 231 bool BookmarkMenuController::CanDrag(MenuItemView* menu) { 232 return true; 233 } 234 235 void BookmarkMenuController::WriteDragData(MenuItemView* sender, 236 ui::OSExchangeData* data) { 237 DCHECK(sender && data); 238 239 UserMetrics::RecordAction(UserMetricsAction("BookmarkBar_DragFromFolder"), 240 profile_); 241 242 BookmarkNodeData drag_data(menu_id_to_node_map_[sender->GetCommand()]); 243 drag_data.Write(profile_, data); 244 } 245 246 int BookmarkMenuController::GetDragOperations(MenuItemView* sender) { 247 return bookmark_utils::BookmarkDragOperation(profile_, 248 menu_id_to_node_map_[sender->GetCommand()]); 249 } 250 251 views::MenuItemView* BookmarkMenuController::GetSiblingMenu( 252 views::MenuItemView* menu, 253 const gfx::Point& screen_point, 254 views::MenuItemView::AnchorPosition* anchor, 255 bool* has_mnemonics, 256 views::MenuButton** button) { 257 if (!bookmark_bar_ || for_drop_) 258 return NULL; 259 gfx::Point bookmark_bar_loc(screen_point); 260 views::View::ConvertPointToView(NULL, bookmark_bar_, &bookmark_bar_loc); 261 int start_index; 262 const BookmarkNode* node = 263 bookmark_bar_->GetNodeForButtonAt(bookmark_bar_loc, &start_index); 264 if (!node || !node->is_folder()) 265 return NULL; 266 267 MenuItemView* alt_menu = node_to_menu_map_[node]; 268 if (!alt_menu) 269 alt_menu = CreateMenu(node, start_index); 270 271 menu_ = alt_menu; 272 273 *button = bookmark_bar_->GetMenuButtonForNode(node); 274 bookmark_bar_->GetAnchorPositionAndStartIndexForButton( 275 *button, anchor, &start_index); 276 *has_mnemonics = false; 277 return alt_menu; 278 } 279 280 int BookmarkMenuController::GetMaxWidthForMenu() { 281 return kMaxMenuWidth; 282 } 283 284 void BookmarkMenuController::BookmarkModelChanged() { 285 menu_->Cancel(); 286 } 287 288 void BookmarkMenuController::BookmarkNodeFaviconLoaded( 289 BookmarkModel* model, const BookmarkNode* node) { 290 NodeToMenuIDMap::iterator menu_pair = node_to_menu_id_map_.find(node); 291 if (menu_pair == node_to_menu_id_map_.end()) 292 return; // We're not showing a menu item for the node. 293 294 // Iterate through the menus looking for the menu containing node. 295 for (NodeToMenuMap::iterator i = node_to_menu_map_.begin(); 296 i != node_to_menu_map_.end(); ++i) { 297 MenuItemView* menu_item = i->second->GetMenuItemByID(menu_pair->second); 298 if (menu_item) { 299 menu_item->SetIcon(model->GetFavicon(node)); 300 return; 301 } 302 } 303 } 304 305 void BookmarkMenuController::WillRemoveBookmarks( 306 const std::vector<const BookmarkNode*>& bookmarks) { 307 std::set<MenuItemView*> removed_menus; 308 309 WillRemoveBookmarksImpl(bookmarks, &removed_menus); 310 311 STLDeleteElements(&removed_menus); 312 } 313 314 void BookmarkMenuController::DidRemoveBookmarks() { 315 profile_->GetBookmarkModel()->AddObserver(this); 316 } 317 318 MenuItemView* BookmarkMenuController::CreateMenu(const BookmarkNode* parent, 319 int start_child_index) { 320 MenuItemView* menu = new MenuItemView(this); 321 menu->SetCommand(next_menu_id_++); 322 menu_id_to_node_map_[menu->GetCommand()] = parent; 323 menu->set_has_icons(true); 324 BuildMenu(parent, start_child_index, menu, &next_menu_id_); 325 node_to_menu_map_[parent] = menu; 326 return menu; 327 } 328 329 void BookmarkMenuController::BuildMenu(const BookmarkNode* parent, 330 int start_child_index, 331 MenuItemView* menu, 332 int* next_menu_id) { 333 DCHECK(!parent->child_count() || 334 start_child_index < parent->child_count()); 335 for (int i = start_child_index; i < parent->child_count(); ++i) { 336 const BookmarkNode* node = parent->GetChild(i); 337 int id = *next_menu_id; 338 339 (*next_menu_id)++; 340 if (node->is_url()) { 341 SkBitmap icon = profile_->GetBookmarkModel()->GetFavicon(node); 342 if (icon.width() == 0) { 343 icon = *ResourceBundle::GetSharedInstance(). 344 GetBitmapNamed(IDR_DEFAULT_FAVICON); 345 } 346 menu->AppendMenuItemWithIcon(id, UTF16ToWide(node->GetTitle()), icon); 347 node_to_menu_id_map_[node] = id; 348 } else if (node->is_folder()) { 349 SkBitmap* folder_icon = ResourceBundle::GetSharedInstance(). 350 GetBitmapNamed(IDR_BOOKMARK_BAR_FOLDER); 351 MenuItemView* submenu = menu->AppendSubMenuWithIcon(id, 352 UTF16ToWide(node->GetTitle()), *folder_icon); 353 node_to_menu_id_map_[node] = id; 354 BuildMenu(node, 0, submenu, next_menu_id); 355 } else { 356 NOTREACHED(); 357 } 358 menu_id_to_node_map_[id] = node; 359 } 360 } 361 362 BookmarkMenuController::~BookmarkMenuController() { 363 profile_->GetBookmarkModel()->RemoveObserver(this); 364 if (observer_) 365 observer_->BookmarkMenuDeleted(this); 366 STLDeleteValues(&node_to_menu_map_); 367 } 368 369 MenuItemView* BookmarkMenuController::GetMenuByID(int id) { 370 for (NodeToMenuMap::const_iterator i = node_to_menu_map_.begin(); 371 i != node_to_menu_map_.end(); ++i) { 372 MenuItemView* menu = i->second->GetMenuItemByID(id); 373 if (menu) 374 return menu; 375 } 376 return NULL; 377 } 378 379 void BookmarkMenuController::WillRemoveBookmarksImpl( 380 const std::vector<const BookmarkNode*>& bookmarks, 381 std::set<views::MenuItemView*>* removed_menus) { 382 // Remove the observer so that when the remove happens we don't prematurely 383 // cancel the menu. 384 profile_->GetBookmarkModel()->RemoveObserver(this); 385 386 // Remove the menu items. 387 std::set<MenuItemView*> changed_parent_menus; 388 for (std::vector<const BookmarkNode*>::const_iterator i = bookmarks.begin(); 389 i != bookmarks.end(); ++i) { 390 NodeToMenuIDMap::iterator node_to_menu = node_to_menu_id_map_.find(*i); 391 if (node_to_menu != node_to_menu_id_map_.end()) { 392 MenuItemView* menu = GetMenuByID(node_to_menu->second); 393 DCHECK(menu); // If there an entry in node_to_menu_id_map_, there should 394 // be a menu. 395 removed_menus->insert(menu); 396 changed_parent_menus.insert(menu->GetParentMenuItem()); 397 menu->parent()->RemoveChildView(menu); 398 node_to_menu_id_map_.erase(node_to_menu); 399 } 400 } 401 402 // All the bookmarks in |bookmarks| should have the same parent. It's possible 403 // to support different parents, but this would need to prune any nodes whose 404 // parent has been removed. As all nodes currently have the same parent, there 405 // is the DCHECK. 406 DCHECK(changed_parent_menus.size() <= 1); 407 408 for (std::set<MenuItemView*>::const_iterator i = changed_parent_menus.begin(); 409 i != changed_parent_menus.end(); ++i) { 410 (*i)->ChildrenChanged(); 411 } 412 413 // Remove any descendants of the removed nodes in node_to_menu_id_map_. 414 for (NodeToMenuIDMap::iterator i = node_to_menu_id_map_.begin(); 415 i != node_to_menu_id_map_.end(); ) { 416 bool ancestor_removed = false; 417 for (std::vector<const BookmarkNode*>::const_iterator j = bookmarks.begin(); 418 j != bookmarks.end(); ++j) { 419 if (i->first->HasAncestor(*j)) { 420 ancestor_removed = true; 421 break; 422 } 423 } 424 if (ancestor_removed) { 425 node_to_menu_id_map_.erase(i++); 426 } else { 427 ++i; 428 } 429 } 430 } 431