Add ENABLE_MEDIA_ROUTER define to builds other than Android and iOS.
[chromium-blink-merge.git] / chrome / browser / ui / views / bookmarks / bookmark_menu_delegate.cc
blobec43608632fc8ed9980c575982efb7a0600ad132
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.
5 #include "chrome/browser/ui/views/bookmarks/bookmark_menu_delegate.h"
7 #include "base/prefs/pref_service.h"
8 #include "base/strings/utf_string_conversions.h"
9 #include "chrome/app/chrome_command_ids.h"
10 #include "chrome/browser/bookmarks/bookmark_model_factory.h"
11 #include "chrome/browser/bookmarks/chrome_bookmark_client.h"
12 #include "chrome/browser/bookmarks/chrome_bookmark_client_factory.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/event_utils.h"
19 #include "chrome/common/pref_names.h"
20 #include "components/bookmarks/browser/bookmark_model.h"
21 #include "content/public/browser/page_navigator.h"
22 #include "content/public/browser/user_metrics.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 "ui/base/window_open_disposition.h"
27 #include "ui/resources/grit/ui_resources.h"
28 #include "ui/views/controls/button/menu_button.h"
29 #include "ui/views/controls/menu/menu_item_view.h"
30 #include "ui/views/controls/menu/submenu_view.h"
31 #include "ui/views/widget/widget.h"
33 using base::UserMetricsAction;
34 using bookmarks::BookmarkModel;
35 using bookmarks::BookmarkNode;
36 using bookmarks::BookmarkNodeData;
37 using content::PageNavigator;
38 using views::MenuItemView;
40 // Max width of a menu. There does not appear to be an OS value for this, yet
41 // both IE and FF restrict the max width of a menu.
42 static const int kMaxMenuWidth = 400;
44 BookmarkMenuDelegate::BookmarkMenuDelegate(Browser* browser,
45 PageNavigator* navigator,
46 views::Widget* parent)
47 : browser_(browser),
48 profile_(browser->profile()),
49 page_navigator_(navigator),
50 parent_(parent),
51 menu_(NULL),
52 parent_menu_item_(NULL),
53 next_menu_id_(IDC_FIRST_BOOKMARK_MENU),
54 real_delegate_(NULL),
55 is_mutating_model_(false),
56 location_(BOOKMARK_LAUNCH_LOCATION_NONE) {
59 BookmarkMenuDelegate::~BookmarkMenuDelegate() {
60 GetBookmarkModel()->RemoveObserver(this);
63 void BookmarkMenuDelegate::Init(views::MenuDelegate* real_delegate,
64 MenuItemView* parent,
65 const BookmarkNode* node,
66 int start_child_index,
67 ShowOptions show_options,
68 BookmarkLaunchLocation location) {
69 GetBookmarkModel()->AddObserver(this);
70 real_delegate_ = real_delegate;
71 location_ = location;
72 if (parent) {
73 parent_menu_item_ = parent;
75 // Add a separator if there are existing items in the menu, and if the
76 // current node has children. If |node| is the bookmark bar then the
77 // managed node is shown as its first child, if it's not empty.
78 BookmarkModel* model = GetBookmarkModel();
79 ChromeBookmarkClient* client = GetChromeBookmarkClient();
80 bool show_forced_folders = show_options == SHOW_PERMANENT_FOLDERS &&
81 node == model->bookmark_bar_node();
82 bool show_managed = show_forced_folders && !client->managed_node()->empty();
83 bool show_supervised =
84 show_forced_folders && !client->supervised_node()->empty();
85 bool has_children = (start_child_index < node->child_count()) ||
86 show_managed || show_supervised;
87 int initial_count = parent->GetSubmenu() ?
88 parent->GetSubmenu()->GetMenuItemCount() : 0;
89 if (has_children && initial_count > 0)
90 parent->AppendSeparator();
91 if (show_managed)
92 BuildMenuForManagedNode(parent);
93 if (show_supervised)
94 BuildMenuForSupervisedNode(parent);
95 BuildMenu(node, start_child_index, parent);
96 if (show_options == SHOW_PERMANENT_FOLDERS)
97 BuildMenusForPermanentNodes(parent);
98 } else {
99 menu_ = CreateMenu(node, start_child_index, show_options);
103 void BookmarkMenuDelegate::SetPageNavigator(PageNavigator* navigator) {
104 page_navigator_ = navigator;
105 if (context_menu_.get())
106 context_menu_->SetPageNavigator(navigator);
109 BookmarkModel* BookmarkMenuDelegate::GetBookmarkModel() {
110 return BookmarkModelFactory::GetForProfile(profile_);
113 ChromeBookmarkClient* BookmarkMenuDelegate::GetChromeBookmarkClient() {
114 return ChromeBookmarkClientFactory::GetForProfile(profile_);
117 void BookmarkMenuDelegate::SetActiveMenu(const BookmarkNode* node,
118 int start_index) {
119 DCHECK(!parent_menu_item_);
120 if (!node_to_menu_map_[node])
121 CreateMenu(node, start_index, HIDE_PERMANENT_FOLDERS);
122 menu_ = node_to_menu_map_[node];
125 base::string16 BookmarkMenuDelegate::GetTooltipText(
126 int id,
127 const gfx::Point& screen_loc) const {
128 MenuIDToNodeMap::const_iterator i = menu_id_to_node_map_.find(id);
129 // When removing bookmarks it may be possible to end up here without a node.
130 if (i == menu_id_to_node_map_.end()) {
131 DCHECK(is_mutating_model_);
132 return base::string16();
135 const BookmarkNode* node = i->second;
136 if (node->is_url()) {
137 return BookmarkBarView::CreateToolTipForURLAndTitle(
138 parent_, screen_loc, node->url(), node->GetTitle(), profile_);
140 return base::string16();
143 bool BookmarkMenuDelegate::IsTriggerableEvent(views::MenuItemView* menu,
144 const ui::Event& e) {
145 return e.type() == ui::ET_GESTURE_TAP ||
146 e.type() == ui::ET_GESTURE_TAP_DOWN ||
147 event_utils::IsPossibleDispositionEvent(e);
150 void BookmarkMenuDelegate::ExecuteCommand(int id, int mouse_event_flags) {
151 DCHECK(menu_id_to_node_map_.find(id) != menu_id_to_node_map_.end());
153 const BookmarkNode* node = menu_id_to_node_map_[id];
154 std::vector<const BookmarkNode*> selection;
155 selection.push_back(node);
157 chrome::OpenAll(parent_->GetNativeWindow(), page_navigator_, selection,
158 ui::DispositionFromEventFlags(mouse_event_flags),
159 profile_);
160 RecordBookmarkLaunch(node, location_);
163 bool BookmarkMenuDelegate::ShouldExecuteCommandWithoutClosingMenu(
164 int id,
165 const ui::Event& event) {
166 return (event.flags() & ui::EF_LEFT_MOUSE_BUTTON) &&
167 ui::DispositionFromEventFlags(event.flags()) == NEW_BACKGROUND_TAB;
170 bool BookmarkMenuDelegate::GetDropFormats(
171 MenuItemView* menu,
172 int* formats,
173 std::set<ui::OSExchangeData::CustomFormat>* custom_formats) {
174 *formats = ui::OSExchangeData::URL;
175 custom_formats->insert(BookmarkNodeData::GetBookmarkCustomFormat());
176 return true;
179 bool BookmarkMenuDelegate::AreDropTypesRequired(MenuItemView* menu) {
180 return true;
183 bool BookmarkMenuDelegate::CanDrop(MenuItemView* menu,
184 const ui::OSExchangeData& data) {
185 // Only accept drops of 1 node, which is the case for all data dragged from
186 // bookmark bar and menus.
188 if (!drop_data_.Read(data) || drop_data_.size() != 1 ||
189 !profile_->GetPrefs()->GetBoolean(
190 bookmarks::prefs::kEditBookmarksEnabled))
191 return false;
193 if (drop_data_.has_single_url())
194 return true;
196 const BookmarkNode* drag_node =
197 drop_data_.GetFirstNode(GetBookmarkModel(), profile_->GetPath());
198 if (!drag_node) {
199 // Dragging a folder from another profile, always accept.
200 return true;
203 // Drag originated from same profile and is not a URL. Only accept it if
204 // the dragged node is not a parent of the node menu represents.
205 if (menu_id_to_node_map_.find(menu->GetCommand()) ==
206 menu_id_to_node_map_.end()) {
207 // If we don't know the menu assume its because we're embedded. We'll
208 // figure out the real operation when GetDropOperation is invoked.
209 return true;
211 const BookmarkNode* drop_node = menu_id_to_node_map_[menu->GetCommand()];
212 DCHECK(drop_node);
213 while (drop_node && drop_node != drag_node)
214 drop_node = drop_node->parent();
215 return (drop_node == NULL);
218 int BookmarkMenuDelegate::GetDropOperation(
219 MenuItemView* item,
220 const ui::DropTargetEvent& event,
221 views::MenuDelegate::DropPosition* position) {
222 // Should only get here if we have drop data.
223 DCHECK(drop_data_.is_valid());
225 const BookmarkNode* node = menu_id_to_node_map_[item->GetCommand()];
226 const BookmarkNode* drop_parent = node->parent();
227 int index_to_drop_at = drop_parent->GetIndexOf(node);
228 BookmarkModel* model = GetBookmarkModel();
229 switch (*position) {
230 case views::MenuDelegate::DROP_AFTER:
231 if (node == model->other_node() || node == model->mobile_node()) {
232 // Dropping after these nodes makes no sense.
233 *position = views::MenuDelegate::DROP_NONE;
235 index_to_drop_at++;
236 break;
238 case views::MenuDelegate::DROP_BEFORE:
239 if (node == model->mobile_node()) {
240 // Dropping before this node makes no sense.
241 *position = views::MenuDelegate::DROP_NONE;
243 break;
245 case views::MenuDelegate::DROP_ON:
246 drop_parent = node;
247 index_to_drop_at = node->child_count();
248 break;
250 default:
251 break;
253 DCHECK(drop_parent);
254 return chrome::GetBookmarkDropOperation(
255 profile_, event, drop_data_, drop_parent, index_to_drop_at);
258 int BookmarkMenuDelegate::OnPerformDrop(
259 MenuItemView* menu,
260 views::MenuDelegate::DropPosition position,
261 const ui::DropTargetEvent& event) {
262 const BookmarkNode* drop_node = menu_id_to_node_map_[menu->GetCommand()];
263 DCHECK(drop_node);
264 BookmarkModel* model = GetBookmarkModel();
265 DCHECK(model);
266 const BookmarkNode* drop_parent = drop_node->parent();
267 DCHECK(drop_parent);
268 int index_to_drop_at = drop_parent->GetIndexOf(drop_node);
269 switch (position) {
270 case views::MenuDelegate::DROP_AFTER:
271 index_to_drop_at++;
272 break;
274 case views::MenuDelegate::DROP_ON:
275 DCHECK(drop_node->is_folder());
276 drop_parent = drop_node;
277 index_to_drop_at = drop_node->child_count();
278 break;
280 case views::MenuDelegate::DROP_BEFORE:
281 if (drop_node == model->other_node() ||
282 drop_node == model->mobile_node()) {
283 // This can happen with SHOW_PERMANENT_FOLDERS.
284 drop_parent = model->bookmark_bar_node();
285 index_to_drop_at = drop_parent->child_count();
287 break;
289 default:
290 break;
293 bool copy = event.source_operations() == ui::DragDropTypes::DRAG_COPY;
294 return chrome::DropBookmarks(profile_, drop_data_,
295 drop_parent, index_to_drop_at, copy);
298 bool BookmarkMenuDelegate::ShowContextMenu(MenuItemView* source,
299 int id,
300 const gfx::Point& p,
301 ui::MenuSourceType source_type) {
302 DCHECK(menu_id_to_node_map_.find(id) != menu_id_to_node_map_.end());
303 std::vector<const BookmarkNode*> nodes;
304 nodes.push_back(menu_id_to_node_map_[id]);
305 bool close_on_delete = !parent_menu_item_ &&
306 (nodes[0]->parent() == GetBookmarkModel()->other_node() &&
307 nodes[0]->parent()->child_count() == 1);
308 context_menu_.reset(
309 new BookmarkContextMenu(
310 parent_,
311 browser_,
312 profile_,
313 page_navigator_,
314 nodes[0]->parent(),
315 nodes,
316 close_on_delete));
317 context_menu_->set_observer(this);
318 context_menu_->RunMenuAt(p, source_type);
319 context_menu_.reset(NULL);
320 return true;
323 bool BookmarkMenuDelegate::CanDrag(MenuItemView* menu) {
324 const BookmarkNode* node = menu_id_to_node_map_[menu->GetCommand()];
325 // Don't let users drag the other folder.
326 return node->parent() != GetBookmarkModel()->root_node();
329 void BookmarkMenuDelegate::WriteDragData(MenuItemView* sender,
330 ui::OSExchangeData* data) {
331 DCHECK(sender && data);
333 content::RecordAction(UserMetricsAction("BookmarkBar_DragFromFolder"));
335 BookmarkNodeData drag_data(menu_id_to_node_map_[sender->GetCommand()]);
336 drag_data.Write(profile_->GetPath(), data);
339 int BookmarkMenuDelegate::GetDragOperations(MenuItemView* sender) {
340 return chrome::GetBookmarkDragOperation(
341 profile_, menu_id_to_node_map_[sender->GetCommand()]);
344 int BookmarkMenuDelegate::GetMaxWidthForMenu(MenuItemView* menu) {
345 return kMaxMenuWidth;
348 void BookmarkMenuDelegate::WillShowMenu(MenuItemView* menu) {
349 auto iter = menu_id_to_node_map_.find(menu->GetCommand());
350 if ((iter != menu_id_to_node_map_.end()) && iter->second->child_count() &&
351 !menu->GetSubmenu()->GetMenuItemCount())
352 BuildMenu(iter->second, 0, menu);
355 void BookmarkMenuDelegate::BookmarkModelChanged() {
358 void BookmarkMenuDelegate::BookmarkNodeFaviconChanged(
359 BookmarkModel* model,
360 const BookmarkNode* node) {
361 NodeToMenuMap::iterator menu_pair = node_to_menu_map_.find(node);
362 if (menu_pair == node_to_menu_map_.end())
363 return; // We're not showing a menu item for the node.
365 menu_pair->second->SetIcon(model->GetFavicon(node).AsImageSkia());
368 void BookmarkMenuDelegate::WillRemoveBookmarks(
369 const std::vector<const BookmarkNode*>& bookmarks) {
370 DCHECK(!is_mutating_model_);
371 is_mutating_model_ = true; // Set to false in DidRemoveBookmarks().
373 // Remove the observer so that when the remove happens we don't prematurely
374 // cancel the menu. The observer is added back in DidRemoveBookmarks().
375 GetBookmarkModel()->RemoveObserver(this);
377 // Remove the menu items.
378 std::set<MenuItemView*> changed_parent_menus;
379 for (std::vector<const BookmarkNode*>::const_iterator i(bookmarks.begin());
380 i != bookmarks.end(); ++i) {
381 NodeToMenuMap::iterator node_to_menu = node_to_menu_map_.find(*i);
382 if (node_to_menu != node_to_menu_map_.end()) {
383 MenuItemView* menu = node_to_menu->second;
384 MenuItemView* parent = menu->GetParentMenuItem();
385 // |parent| is NULL when removing a root. This happens when right clicking
386 // to delete an empty folder.
387 if (parent) {
388 changed_parent_menus.insert(parent);
389 parent->RemoveMenuItemAt(menu->parent()->GetIndexOf(menu));
391 node_to_menu_map_.erase(node_to_menu);
392 menu_id_to_node_map_.erase(menu->GetCommand());
396 // All the bookmarks in |bookmarks| should have the same parent. It's possible
397 // to support different parents, but this would need to prune any nodes whose
398 // parent has been removed. As all nodes currently have the same parent, there
399 // is the DCHECK.
400 DCHECK(changed_parent_menus.size() <= 1);
402 // Remove any descendants of the removed nodes in |node_to_menu_map_|.
403 for (NodeToMenuMap::iterator i(node_to_menu_map_.begin());
404 i != node_to_menu_map_.end(); ) {
405 bool ancestor_removed = false;
406 for (std::vector<const BookmarkNode*>::const_iterator j(bookmarks.begin());
407 j != bookmarks.end(); ++j) {
408 if (i->first->HasAncestor(*j)) {
409 ancestor_removed = true;
410 break;
413 if (ancestor_removed) {
414 menu_id_to_node_map_.erase(i->second->GetCommand());
415 node_to_menu_map_.erase(i++);
416 } else {
417 ++i;
421 for (std::set<MenuItemView*>::const_iterator i(changed_parent_menus.begin());
422 i != changed_parent_menus.end(); ++i)
423 (*i)->ChildrenChanged();
426 void BookmarkMenuDelegate::DidRemoveBookmarks() {
427 // Balances remove in WillRemoveBookmarksImpl.
428 GetBookmarkModel()->AddObserver(this);
429 DCHECK(is_mutating_model_);
430 is_mutating_model_ = false;
433 MenuItemView* BookmarkMenuDelegate::CreateMenu(const BookmarkNode* parent,
434 int start_child_index,
435 ShowOptions show_options) {
436 MenuItemView* menu = new MenuItemView(real_delegate_);
437 menu->SetCommand(next_menu_id_++);
438 AddMenuToMaps(menu, parent);
439 menu->set_has_icons(true);
440 bool show_permanent = show_options == SHOW_PERMANENT_FOLDERS;
441 if (show_permanent && parent == GetBookmarkModel()->bookmark_bar_node()) {
442 BuildMenuForManagedNode(menu);
443 BuildMenuForSupervisedNode(menu);
445 BuildMenu(parent, start_child_index, menu);
446 if (show_permanent)
447 BuildMenusForPermanentNodes(menu);
448 return menu;
451 void BookmarkMenuDelegate::BuildMenusForPermanentNodes(
452 views::MenuItemView* menu) {
453 BookmarkModel* model = GetBookmarkModel();
454 bool added_separator = false;
455 BuildMenuForPermanentNode(model->other_node(), IDR_BOOKMARK_BAR_FOLDER, menu,
456 &added_separator);
457 BuildMenuForPermanentNode(model->mobile_node(), IDR_BOOKMARK_BAR_FOLDER, menu,
458 &added_separator);
461 void BookmarkMenuDelegate::BuildMenuForPermanentNode(
462 const BookmarkNode* node,
463 int icon_resource_id,
464 MenuItemView* menu,
465 bool* added_separator) {
466 if (!node->IsVisible() || node->GetTotalNodeCount() == 1)
467 return; // No children, don't create a menu.
469 if (!*added_separator) {
470 *added_separator = true;
471 menu->AppendSeparator();
474 ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance();
475 gfx::ImageSkia* folder_icon = rb->GetImageSkiaNamed(icon_resource_id);
476 AddMenuToMaps(menu->AppendSubMenuWithIcon(next_menu_id_++, node->GetTitle(),
477 *folder_icon),
478 node);
481 void BookmarkMenuDelegate::BuildMenuForManagedNode(MenuItemView* menu) {
482 // Don't add a separator for this menu.
483 bool added_separator = true;
484 const BookmarkNode* node = GetChromeBookmarkClient()->managed_node();
485 BuildMenuForPermanentNode(node, IDR_BOOKMARK_BAR_FOLDER_MANAGED, menu,
486 &added_separator);
489 void BookmarkMenuDelegate::BuildMenuForSupervisedNode(MenuItemView* menu) {
490 // Don't add a separator for this menu.
491 bool added_separator = true;
492 const BookmarkNode* node = GetChromeBookmarkClient()->supervised_node();
493 BuildMenuForPermanentNode(node, IDR_BOOKMARK_BAR_FOLDER_SUPERVISED, menu,
494 &added_separator);
497 void BookmarkMenuDelegate::BuildMenu(const BookmarkNode* parent,
498 int start_child_index,
499 MenuItemView* menu) {
500 DCHECK(parent->empty() || start_child_index < parent->child_count());
501 ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance();
502 for (int i = start_child_index; i < parent->child_count(); ++i) {
503 const BookmarkNode* node = parent->GetChild(i);
504 const int id = next_menu_id_++;
505 MenuItemView* child_menu_item;
506 if (node->is_url()) {
507 const gfx::Image& image = GetBookmarkModel()->GetFavicon(node);
508 const gfx::ImageSkia* icon = image.IsEmpty() ?
509 rb->GetImageSkiaNamed(IDR_DEFAULT_FAVICON) : image.ToImageSkia();
510 child_menu_item =
511 menu->AppendMenuItemWithIcon(id, node->GetTitle(), *icon);
512 } else {
513 DCHECK(node->is_folder());
514 gfx::ImageSkia* folder_icon =
515 rb->GetImageSkiaNamed(IDR_BOOKMARK_BAR_FOLDER);
516 child_menu_item =
517 menu->AppendSubMenuWithIcon(id, node->GetTitle(), *folder_icon);
519 AddMenuToMaps(child_menu_item, node);
523 void BookmarkMenuDelegate::AddMenuToMaps(MenuItemView* menu,
524 const BookmarkNode* node) {
525 menu_id_to_node_map_[menu->GetCommand()] = node;
526 node_to_menu_map_[node] = menu;