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