bookmark_menu_delegate.cc revision f8ee788a64d60abd8f2d742a5fdedde054ecd910
172735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project// Copyright (c) 2012 The Chromium Authors. All rights reserved.
272735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project// Use of this source code is governed by a BSD-style license that can be
372735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project// found in the LICENSE file.
472735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project
572735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project#include "chrome/browser/ui/views/bookmarks/bookmark_menu_delegate.h"
672735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project
772735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project#include "base/prefs/pref_service.h"
872735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project#include "base/strings/utf_string_conversions.h"
972735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project#include "chrome/browser/bookmarks/bookmark_model_factory.h"
1072735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project#include "chrome/browser/bookmarks/chrome_bookmark_client.h"
1172735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project#include "chrome/browser/bookmarks/chrome_bookmark_client_factory.h"
1272735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project#include "chrome/browser/profiles/profile.h"
1372735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project#include "chrome/browser/ui/bookmarks/bookmark_drag_drop.h"
1472735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project#include "chrome/browser/ui/bookmarks/bookmark_utils.h"
1572735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project#include "chrome/browser/ui/browser.h"
1672735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project#include "chrome/browser/ui/views/bookmarks/bookmark_bar_view.h"
1772735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project#include "chrome/browser/ui/views/bookmarks/bookmark_drag_drop_views.h"
1872735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project#include "chrome/browser/ui/views/event_utils.h"
1972735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project#include "chrome/common/pref_names.h"
2072735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project#include "components/bookmarks/browser/bookmark_model.h"
2172735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project#include "content/public/browser/page_navigator.h"
22798098f7dcc1b7f1fbed1727f1e9ee9a9902f24aAlex Yakavenka#include "content/public/browser/user_metrics.h"
23d6f77b1faf5a6a230026815e8b63d50f71c2361fFrederic Predon#include "grit/generated_resources.h"
2472735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project#include "grit/theme_resources.h"
2572735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project#include "grit/ui_resources.h"
2672735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project#include "ui/base/dragdrop/os_exchange_data.h"
2772735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project#include "ui/base/l10n/l10n_util.h"
2872735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project#include "ui/base/resource/resource_bundle.h"
2972735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project#include "ui/base/window_open_disposition.h"
3072735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project#include "ui/views/controls/button/menu_button.h"
3172735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project#include "ui/views/controls/menu/menu_item_view.h"
3272735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project#include "ui/views/controls/menu/submenu_view.h"
3372735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project#include "ui/views/widget/widget.h"
3472735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project
3572735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Projectusing base::UserMetricsAction;
3672735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Projectusing content::PageNavigator;
3772735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Projectusing views::MenuItemView;
3872735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project
3972735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project// Max width of a menu. There does not appear to be an OS value for this, yet
4072735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project// both IE and FF restrict the max width of a menu.
4172735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Projectstatic const int kMaxMenuWidth = 400;
4272735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project
4372735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source ProjectBookmarkMenuDelegate::BookmarkMenuDelegate(Browser* browser,
4472735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project                                           PageNavigator* navigator,
4572735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project                                           views::Widget* parent,
4672735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project                                           int first_menu_id,
4772735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project                                           int max_menu_id)
4872735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project    : browser_(browser),
4972735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project      profile_(browser->profile()),
5072735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project      page_navigator_(navigator),
5172735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project      parent_(parent),
5272735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project      menu_(NULL),
5372735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project      parent_menu_item_(NULL),
5472735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project      next_menu_id_(first_menu_id),
5572735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project      min_menu_id_(first_menu_id),
5672735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project      max_menu_id_(max_menu_id),
5772735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project      real_delegate_(NULL),
5872735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project      is_mutating_model_(false),
5972735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project      location_(BOOKMARK_LAUNCH_LOCATION_NONE) {}
6072735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project
6172735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source ProjectBookmarkMenuDelegate::~BookmarkMenuDelegate() {
6272735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project  GetBookmarkModel()->RemoveObserver(this);
6372735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project}
6472735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project
6572735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Projectvoid BookmarkMenuDelegate::Init(views::MenuDelegate* real_delegate,
6672735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project                                MenuItemView* parent,
6772735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project                                const BookmarkNode* node,
6872735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project                                int start_child_index,
6972735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project                                ShowOptions show_options,
7072735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project                                BookmarkLaunchLocation location) {
7172735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project  GetBookmarkModel()->AddObserver(this);
7272735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project  real_delegate_ = real_delegate;
7372735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project  location_ = location;
7472735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project  if (parent) {
7572735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project    parent_menu_item_ = parent;
7672735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project
7772735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project    // Add a separator if there are existing items in the menu, and if the
7872735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project    // current node has children. If |node| is the bookmark bar then the
7972735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project    // managed node is shown as its first child, if it's not empty.
8072735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project    BookmarkModel* model = GetBookmarkModel();
8172735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project    ChromeBookmarkClient* client = GetChromeBookmarkClient();
8272735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project    bool show_managed = show_options == SHOW_PERMANENT_FOLDERS &&
8372735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project                        node == model->bookmark_bar_node() &&
8472735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project                        !client->managed_node()->empty();
8572735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project    bool has_children =
8672735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project        (start_child_index < node->child_count()) || show_managed;
8772735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project    int initial_count = parent->GetSubmenu() ?
8872735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project        parent->GetSubmenu()->GetMenuItemCount() : 0;
8972735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project    if (has_children && initial_count > 0)
9072735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project      parent->AppendSeparator();
9172735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project    if (show_managed)
9272735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project      BuildMenuForManagedNode(parent, &next_menu_id_);
9372735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project    BuildMenu(node, start_child_index, parent, &next_menu_id_);
9472735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project    if (show_options == SHOW_PERMANENT_FOLDERS)
9572735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project      BuildMenusForPermanentNodes(parent, &next_menu_id_);
9672735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project  } else {
9772735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project    menu_ = CreateMenu(node, start_child_index, show_options);
9872735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project  }
9972735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project}
10072735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project
10172735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Projectvoid BookmarkMenuDelegate::SetPageNavigator(PageNavigator* navigator) {
10272735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project  page_navigator_ = navigator;
10372735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project  if (context_menu_.get())
10472735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project    context_menu_->SetPageNavigator(navigator);
10572735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project}
10672735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project
10772735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source ProjectBookmarkModel* BookmarkMenuDelegate::GetBookmarkModel() {
10872735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project  return BookmarkModelFactory::GetForProfile(profile_);
10972735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project}
11072735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project
11172735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source ProjectChromeBookmarkClient* BookmarkMenuDelegate::GetChromeBookmarkClient() {
11272735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project  return ChromeBookmarkClientFactory::GetForProfile(profile_);
11372735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project}
11472735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project
11572735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Projectvoid BookmarkMenuDelegate::SetActiveMenu(const BookmarkNode* node,
11672735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project                                         int start_index) {
11772735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project  DCHECK(!parent_menu_item_);
11872735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project  if (!node_to_menu_map_[node])
11972735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project    CreateMenu(node, start_index, HIDE_PERMANENT_FOLDERS);
12072735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project  menu_ = node_to_menu_map_[node];
12172735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project}
12272735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project
12372735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Projectbase::string16 BookmarkMenuDelegate::GetTooltipText(
12472735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project    int id,
12572735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project    const gfx::Point& screen_loc) const {
12672735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project  MenuIDToNodeMap::const_iterator i = menu_id_to_node_map_.find(id);
12772735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project  // When removing bookmarks it may be possible to end up here without a node.
12872735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project  if (i == menu_id_to_node_map_.end()) {
12972735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project    DCHECK(is_mutating_model_);
13072735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project    return base::string16();
13172735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project  }
13272735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project
13372735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project  const BookmarkNode* node = i->second;
13472735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project  if (node->is_url()) {
13572735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project    return BookmarkBarView::CreateToolTipForURLAndTitle(
13672735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project        parent_, screen_loc, node->url(), node->GetTitle(), profile_);
13772735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project  }
13872735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project  return base::string16();
13972735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project}
14072735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project
14172735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Projectbool BookmarkMenuDelegate::IsTriggerableEvent(views::MenuItemView* menu,
14272735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project                                              const ui::Event& e) {
14372735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project  return e.type() == ui::ET_GESTURE_TAP ||
14472735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project         e.type() == ui::ET_GESTURE_TAP_DOWN ||
14572735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project         event_utils::IsPossibleDispositionEvent(e);
14672735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project}
14772735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project
14836cb4bcc16e6d395e03d365a066bcad78e9d25c4Tom Taylorvoid BookmarkMenuDelegate::ExecuteCommand(int id, int mouse_event_flags) {
14936cb4bcc16e6d395e03d365a066bcad78e9d25c4Tom Taylor  DCHECK(menu_id_to_node_map_.find(id) != menu_id_to_node_map_.end());
15036cb4bcc16e6d395e03d365a066bcad78e9d25c4Tom Taylor
15136cb4bcc16e6d395e03d365a066bcad78e9d25c4Tom Taylor  const BookmarkNode* node = menu_id_to_node_map_[id];
15236cb4bcc16e6d395e03d365a066bcad78e9d25c4Tom Taylor  std::vector<const BookmarkNode*> selection;
153d6f77b1faf5a6a230026815e8b63d50f71c2361fFrederic Predon  selection.push_back(node);
15436cb4bcc16e6d395e03d365a066bcad78e9d25c4Tom Taylor
15536cb4bcc16e6d395e03d365a066bcad78e9d25c4Tom Taylor  chrome::OpenAll(parent_->GetNativeWindow(), page_navigator_, selection,
15636cb4bcc16e6d395e03d365a066bcad78e9d25c4Tom Taylor                  ui::DispositionFromEventFlags(mouse_event_flags),
15736cb4bcc16e6d395e03d365a066bcad78e9d25c4Tom Taylor                  profile_);
15872735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project  RecordBookmarkLaunch(node, location_);
159}
160
161bool BookmarkMenuDelegate::ShouldExecuteCommandWithoutClosingMenu(
162    int id,
163    const ui::Event& event) {
164  return (event.flags() & ui::EF_LEFT_MOUSE_BUTTON) &&
165         ui::DispositionFromEventFlags(event.flags()) == NEW_BACKGROUND_TAB;
166}
167
168bool BookmarkMenuDelegate::GetDropFormats(
169    MenuItemView* menu,
170    int* formats,
171    std::set<ui::OSExchangeData::CustomFormat>* custom_formats) {
172  *formats = ui::OSExchangeData::URL;
173  custom_formats->insert(BookmarkNodeData::GetBookmarkCustomFormat());
174  return true;
175}
176
177bool BookmarkMenuDelegate::AreDropTypesRequired(MenuItemView* menu) {
178  return true;
179}
180
181bool BookmarkMenuDelegate::CanDrop(MenuItemView* menu,
182                                   const ui::OSExchangeData& data) {
183  // Only accept drops of 1 node, which is the case for all data dragged from
184  // bookmark bar and menus.
185
186  if (!drop_data_.Read(data) || drop_data_.elements.size() != 1 ||
187      !profile_->GetPrefs()->GetBoolean(prefs::kEditBookmarksEnabled))
188    return false;
189
190  if (drop_data_.has_single_url())
191    return true;
192
193  const BookmarkNode* drag_node =
194      drop_data_.GetFirstNode(GetBookmarkModel(), profile_->GetPath());
195  if (!drag_node) {
196    // Dragging a folder from another profile, always accept.
197    return true;
198  }
199
200  // Drag originated from same profile and is not a URL. Only accept it if
201  // the dragged node is not a parent of the node menu represents.
202  if (menu_id_to_node_map_.find(menu->GetCommand()) ==
203      menu_id_to_node_map_.end()) {
204    // If we don't know the menu assume its because we're embedded. We'll
205    // figure out the real operation when GetDropOperation is invoked.
206    return true;
207  }
208  const BookmarkNode* drop_node = menu_id_to_node_map_[menu->GetCommand()];
209  DCHECK(drop_node);
210  while (drop_node && drop_node != drag_node)
211    drop_node = drop_node->parent();
212  return (drop_node == NULL);
213}
214
215int BookmarkMenuDelegate::GetDropOperation(
216    MenuItemView* item,
217    const ui::DropTargetEvent& event,
218    views::MenuDelegate::DropPosition* position) {
219  // Should only get here if we have drop data.
220  DCHECK(drop_data_.is_valid());
221
222  const BookmarkNode* node = menu_id_to_node_map_[item->GetCommand()];
223  const BookmarkNode* drop_parent = node->parent();
224  int index_to_drop_at = drop_parent->GetIndexOf(node);
225  BookmarkModel* model = GetBookmarkModel();
226  switch (*position) {
227    case views::MenuDelegate::DROP_AFTER:
228      if (node == model->other_node() || node == model->mobile_node()) {
229        // Dropping after these nodes makes no sense.
230        *position = views::MenuDelegate::DROP_NONE;
231      }
232      index_to_drop_at++;
233      break;
234
235    case views::MenuDelegate::DROP_BEFORE:
236      if (node == model->mobile_node()) {
237        // Dropping before this node makes no sense.
238        *position = views::MenuDelegate::DROP_NONE;
239      }
240      break;
241
242    case views::MenuDelegate::DROP_ON:
243      drop_parent = node;
244      index_to_drop_at = node->child_count();
245      break;
246
247    default:
248      break;
249  }
250  DCHECK(drop_parent);
251  return chrome::GetBookmarkDropOperation(
252      profile_, event, drop_data_, drop_parent, index_to_drop_at);
253}
254
255int BookmarkMenuDelegate::OnPerformDrop(
256    MenuItemView* menu,
257    views::MenuDelegate::DropPosition position,
258    const ui::DropTargetEvent& event) {
259  const BookmarkNode* drop_node = menu_id_to_node_map_[menu->GetCommand()];
260  DCHECK(drop_node);
261  BookmarkModel* model = GetBookmarkModel();
262  DCHECK(model);
263  const BookmarkNode* drop_parent = drop_node->parent();
264  DCHECK(drop_parent);
265  int index_to_drop_at = drop_parent->GetIndexOf(drop_node);
266  switch (position) {
267    case views::MenuDelegate::DROP_AFTER:
268      index_to_drop_at++;
269      break;
270
271    case views::MenuDelegate::DROP_ON:
272      DCHECK(drop_node->is_folder());
273      drop_parent = drop_node;
274      index_to_drop_at = drop_node->child_count();
275      break;
276
277    case views::MenuDelegate::DROP_BEFORE:
278      if (drop_node == model->other_node() ||
279          drop_node == model->mobile_node()) {
280        // This can happen with SHOW_PERMANENT_FOLDERS.
281        drop_parent = model->bookmark_bar_node();
282        index_to_drop_at = drop_parent->child_count();
283      }
284      break;
285
286    default:
287      break;
288  }
289
290  bool copy = event.source_operations() == ui::DragDropTypes::DRAG_COPY;
291  return chrome::DropBookmarks(profile_, drop_data_,
292                               drop_parent, index_to_drop_at, copy);
293}
294
295bool BookmarkMenuDelegate::ShowContextMenu(MenuItemView* source,
296                                           int id,
297                                           const gfx::Point& p,
298                                           ui::MenuSourceType source_type) {
299  DCHECK(menu_id_to_node_map_.find(id) != menu_id_to_node_map_.end());
300  std::vector<const BookmarkNode*> nodes;
301  nodes.push_back(menu_id_to_node_map_[id]);
302  bool close_on_delete = !parent_menu_item_ &&
303      (nodes[0]->parent() == GetBookmarkModel()->other_node() &&
304       nodes[0]->parent()->child_count() == 1);
305  context_menu_.reset(
306      new BookmarkContextMenu(
307          parent_,
308          browser_,
309          profile_,
310          page_navigator_,
311          nodes[0]->parent(),
312          nodes,
313          close_on_delete));
314  context_menu_->set_observer(this);
315  context_menu_->RunMenuAt(p, source_type);
316  context_menu_.reset(NULL);
317  return true;
318}
319
320bool BookmarkMenuDelegate::CanDrag(MenuItemView* menu) {
321  const BookmarkNode* node = menu_id_to_node_map_[menu->GetCommand()];
322  // Don't let users drag the other folder.
323  return node->parent() != GetBookmarkModel()->root_node();
324}
325
326void BookmarkMenuDelegate::WriteDragData(MenuItemView* sender,
327                                         ui::OSExchangeData* data) {
328  DCHECK(sender && data);
329
330  content::RecordAction(UserMetricsAction("BookmarkBar_DragFromFolder"));
331
332  BookmarkNodeData drag_data(menu_id_to_node_map_[sender->GetCommand()]);
333  drag_data.Write(profile_->GetPath(), data);
334}
335
336int BookmarkMenuDelegate::GetDragOperations(MenuItemView* sender) {
337  return chrome::GetBookmarkDragOperation(
338      profile_, menu_id_to_node_map_[sender->GetCommand()]);
339}
340
341int BookmarkMenuDelegate::GetMaxWidthForMenu(MenuItemView* menu) {
342  return kMaxMenuWidth;
343}
344
345void BookmarkMenuDelegate::BookmarkModelChanged() {
346}
347
348void BookmarkMenuDelegate::BookmarkNodeFaviconChanged(
349    BookmarkModel* model,
350    const BookmarkNode* node) {
351  NodeToMenuMap::iterator menu_pair = node_to_menu_map_.find(node);
352  if (menu_pair == node_to_menu_map_.end())
353    return;  // We're not showing a menu item for the node.
354
355  menu_pair->second->SetIcon(model->GetFavicon(node).AsImageSkia());
356}
357
358void BookmarkMenuDelegate::WillRemoveBookmarks(
359    const std::vector<const BookmarkNode*>& bookmarks) {
360  DCHECK(!is_mutating_model_);
361  is_mutating_model_ = true;  // Set to false in DidRemoveBookmarks().
362
363  // Remove the observer so that when the remove happens we don't prematurely
364  // cancel the menu. The observer is added back in DidRemoveBookmarks().
365  GetBookmarkModel()->RemoveObserver(this);
366
367  // Remove the menu items.
368  std::set<MenuItemView*> changed_parent_menus;
369  for (std::vector<const BookmarkNode*>::const_iterator i(bookmarks.begin());
370       i != bookmarks.end(); ++i) {
371    NodeToMenuMap::iterator node_to_menu = node_to_menu_map_.find(*i);
372    if (node_to_menu != node_to_menu_map_.end()) {
373      MenuItemView* menu = node_to_menu->second;
374      MenuItemView* parent = menu->GetParentMenuItem();
375      // |parent| is NULL when removing a root. This happens when right clicking
376      // to delete an empty folder.
377      if (parent) {
378        changed_parent_menus.insert(parent);
379        parent->RemoveMenuItemAt(menu->parent()->GetIndexOf(menu));
380      }
381      node_to_menu_map_.erase(node_to_menu);
382      menu_id_to_node_map_.erase(menu->GetCommand());
383    }
384  }
385
386  // All the bookmarks in |bookmarks| should have the same parent. It's possible
387  // to support different parents, but this would need to prune any nodes whose
388  // parent has been removed. As all nodes currently have the same parent, there
389  // is the DCHECK.
390  DCHECK(changed_parent_menus.size() <= 1);
391
392  // Remove any descendants of the removed nodes in |node_to_menu_map_|.
393  for (NodeToMenuMap::iterator i(node_to_menu_map_.begin());
394       i != node_to_menu_map_.end(); ) {
395    bool ancestor_removed = false;
396    for (std::vector<const BookmarkNode*>::const_iterator j(bookmarks.begin());
397         j != bookmarks.end(); ++j) {
398      if (i->first->HasAncestor(*j)) {
399        ancestor_removed = true;
400        break;
401      }
402    }
403    if (ancestor_removed) {
404      menu_id_to_node_map_.erase(i->second->GetCommand());
405      node_to_menu_map_.erase(i++);
406    } else {
407      ++i;
408    }
409  }
410
411  for (std::set<MenuItemView*>::const_iterator i(changed_parent_menus.begin());
412       i != changed_parent_menus.end(); ++i)
413    (*i)->ChildrenChanged();
414}
415
416void BookmarkMenuDelegate::DidRemoveBookmarks() {
417  // Balances remove in WillRemoveBookmarksImpl.
418  GetBookmarkModel()->AddObserver(this);
419  DCHECK(is_mutating_model_);
420  is_mutating_model_ = false;
421}
422
423MenuItemView* BookmarkMenuDelegate::CreateMenu(const BookmarkNode* parent,
424                                               int start_child_index,
425                                               ShowOptions show_options) {
426  MenuItemView* menu = new MenuItemView(real_delegate_);
427  menu->SetCommand(next_menu_id_++);
428  menu_id_to_node_map_[menu->GetCommand()] = parent;
429  menu->set_has_icons(true);
430  bool show_permanent = show_options == SHOW_PERMANENT_FOLDERS;
431  if (show_permanent && parent == GetBookmarkModel()->bookmark_bar_node())
432    BuildMenuForManagedNode(menu, &next_menu_id_);
433  BuildMenu(parent, start_child_index, menu, &next_menu_id_);
434  if (show_permanent)
435    BuildMenusForPermanentNodes(menu, &next_menu_id_);
436  return menu;
437}
438
439void BookmarkMenuDelegate::BuildMenusForPermanentNodes(
440    views::MenuItemView* menu,
441    int* next_menu_id) {
442  BookmarkModel* model = GetBookmarkModel();
443  bool added_separator = false;
444  BuildMenuForPermanentNode(model->other_node(), menu, next_menu_id,
445                            &added_separator);
446  BuildMenuForPermanentNode(model->mobile_node(), menu, next_menu_id,
447                            &added_separator);
448}
449
450void BookmarkMenuDelegate::BuildMenuForPermanentNode(
451    const BookmarkNode* node,
452    MenuItemView* menu,
453    int* next_menu_id,
454    bool* added_separator) {
455  if (!node->IsVisible() || node->GetTotalNodeCount() == 1)
456    return;  // No children, don't create a menu.
457
458  int id = *next_menu_id;
459  // Don't create the submenu if its menu ID will be outside the range allowed.
460  if (IsOutsideMenuIdRange(id))
461    return;
462  (*next_menu_id)++;
463
464  if (!*added_separator) {
465    *added_separator = true;
466    menu->AppendSeparator();
467  }
468
469  ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance();
470  gfx::ImageSkia* folder_icon = rb->GetImageSkiaNamed(IDR_BOOKMARK_BAR_FOLDER);
471  MenuItemView* submenu = menu->AppendSubMenuWithIcon(
472      id, node->GetTitle(), *folder_icon);
473  BuildMenu(node, 0, submenu, next_menu_id);
474  menu_id_to_node_map_[id] = node;
475}
476
477void BookmarkMenuDelegate::BuildMenuForManagedNode(
478    MenuItemView* menu,
479    int* next_menu_id) {
480  // Don't add a separator for this menu.
481  bool added_separator = true;
482  const BookmarkNode* node = GetChromeBookmarkClient()->managed_node();
483  // TODO(joaodasilva): use the "managed bookmark folder" icon here.
484  // http://crbug.com/49598
485  BuildMenuForPermanentNode(node, menu, next_menu_id, &added_separator);
486}
487
488void BookmarkMenuDelegate::BuildMenu(const BookmarkNode* parent,
489                                     int start_child_index,
490                                     MenuItemView* menu,
491                                     int* next_menu_id) {
492  node_to_menu_map_[parent] = menu;
493  DCHECK(parent->empty() || start_child_index < parent->child_count());
494  ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance();
495  for (int i = start_child_index; i < parent->child_count(); ++i) {
496    const BookmarkNode* node = parent->GetChild(i);
497    const int id = *next_menu_id;
498    // Don't create the item if its menu ID will be outside the range allowed.
499    if (IsOutsideMenuIdRange(id))
500      break;
501
502    (*next_menu_id)++;
503
504    menu_id_to_node_map_[id] = node;
505    if (node->is_url()) {
506      const gfx::Image& image = GetBookmarkModel()->GetFavicon(node);
507      const gfx::ImageSkia* icon = image.IsEmpty() ?
508          rb->GetImageSkiaNamed(IDR_DEFAULT_FAVICON) : image.ToImageSkia();
509      node_to_menu_map_[node] =
510          menu->AppendMenuItemWithIcon(id, node->GetTitle(), *icon);
511    } else if (node->is_folder()) {
512      gfx::ImageSkia* folder_icon =
513          rb->GetImageSkiaNamed(IDR_BOOKMARK_BAR_FOLDER);
514      MenuItemView* submenu = menu->AppendSubMenuWithIcon(
515          id, node->GetTitle(), *folder_icon);
516      BuildMenu(node, 0, submenu, next_menu_id);
517    } else {
518      NOTREACHED();
519    }
520  }
521}
522
523bool BookmarkMenuDelegate::IsOutsideMenuIdRange(int menu_id) const {
524  return menu_id < min_menu_id_ || menu_id > max_menu_id_;
525}
526