Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / ui / views / bookmarks / bookmark_menu_delegate.cc
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_factory.h"
10 #include "chrome/browser/profiles/profile.h"
11 #include "chrome/browser/ui/bookmarks/bookmark_drag_drop.h"
12 #include "chrome/browser/ui/bookmarks/bookmark_utils.h"
13 #include "chrome/browser/ui/browser.h"
14 #include "chrome/browser/ui/views/bookmarks/bookmark_bar_view.h"
15 #include "chrome/browser/ui/views/bookmarks/bookmark_drag_drop_views.h"
16 #include "chrome/browser/ui/views/event_utils.h"
17 #include "chrome/common/pref_names.h"
18 #include "components/bookmarks/core/browser/bookmark_model.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"
32
33 using base::UserMetricsAction;
34 using content::PageNavigator;
35 using views::MenuItemView;
36
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;
40
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       parent_menu_item_(NULL),
52       next_menu_id_(first_menu_id),
53       min_menu_id_(first_menu_id),
54       max_menu_id_(max_menu_id),
55       real_delegate_(NULL),
56       is_mutating_model_(false),
57       location_(BOOKMARK_LAUNCH_LOCATION_NONE) {}
58
59 BookmarkMenuDelegate::~BookmarkMenuDelegate() {
60   GetBookmarkModel()->RemoveObserver(this);
61 }
62
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;
74     int initial_count = parent->GetSubmenu() ?
75         parent->GetSubmenu()->GetMenuItemCount() : 0;
76     if ((start_child_index < node->child_count()) &&
77         (initial_count > 0)) {
78       parent->AppendSeparator();
79     }
80     BuildMenu(node, start_child_index, parent, &next_menu_id_);
81     if (show_options == SHOW_PERMANENT_FOLDERS)
82       BuildMenusForPermanentNodes(parent, &next_menu_id_);
83   } else {
84     menu_ = CreateMenu(node, start_child_index, show_options);
85   }
86 }
87
88 void BookmarkMenuDelegate::SetPageNavigator(PageNavigator* navigator) {
89   page_navigator_ = navigator;
90   if (context_menu_.get())
91     context_menu_->SetPageNavigator(navigator);
92 }
93
94 BookmarkModel* BookmarkMenuDelegate::GetBookmarkModel() {
95   return BookmarkModelFactory::GetForProfile(profile_);
96 }
97
98 void BookmarkMenuDelegate::SetActiveMenu(const BookmarkNode* node,
99                                          int start_index) {
100   DCHECK(!parent_menu_item_);
101   if (!node_to_menu_map_[node])
102     CreateMenu(node, start_index, HIDE_PERMANENT_FOLDERS);
103   menu_ = node_to_menu_map_[node];
104 }
105
106 base::string16 BookmarkMenuDelegate::GetTooltipText(
107     int id,
108     const gfx::Point& screen_loc) const {
109   MenuIDToNodeMap::const_iterator i = menu_id_to_node_map_.find(id);
110   // When removing bookmarks it may be possible to end up here without a node.
111   if (i == menu_id_to_node_map_.end()) {
112     DCHECK(is_mutating_model_);
113     return base::string16();
114   }
115
116   const BookmarkNode* node = i->second;
117   if (node->is_url()) {
118     return BookmarkBarView::CreateToolTipForURLAndTitle(
119         parent_, screen_loc, node->url(), node->GetTitle(), profile_);
120   }
121   return base::string16();
122 }
123
124 bool BookmarkMenuDelegate::IsTriggerableEvent(views::MenuItemView* menu,
125                                               const ui::Event& e) {
126   return e.type() == ui::ET_GESTURE_TAP ||
127          e.type() == ui::ET_GESTURE_TAP_DOWN ||
128          event_utils::IsPossibleDispositionEvent(e);
129 }
130
131 void BookmarkMenuDelegate::ExecuteCommand(int id, int mouse_event_flags) {
132   DCHECK(menu_id_to_node_map_.find(id) != menu_id_to_node_map_.end());
133
134   const BookmarkNode* node = menu_id_to_node_map_[id];
135   std::vector<const BookmarkNode*> selection;
136   selection.push_back(node);
137
138   chrome::OpenAll(parent_->GetNativeWindow(), page_navigator_, selection,
139                   ui::DispositionFromEventFlags(mouse_event_flags),
140                   profile_);
141   RecordBookmarkLaunch(node, location_);
142 }
143
144 bool BookmarkMenuDelegate::ShouldExecuteCommandWithoutClosingMenu(
145     int id,
146     const ui::Event& event) {
147   return (event.flags() & ui::EF_LEFT_MOUSE_BUTTON) &&
148          ui::DispositionFromEventFlags(event.flags()) == NEW_BACKGROUND_TAB;
149 }
150
151 bool BookmarkMenuDelegate::GetDropFormats(
152     MenuItemView* menu,
153     int* formats,
154     std::set<ui::OSExchangeData::CustomFormat>* custom_formats) {
155   *formats = ui::OSExchangeData::URL;
156   custom_formats->insert(BookmarkNodeData::GetBookmarkCustomFormat());
157   return true;
158 }
159
160 bool BookmarkMenuDelegate::AreDropTypesRequired(MenuItemView* menu) {
161   return true;
162 }
163
164 bool BookmarkMenuDelegate::CanDrop(MenuItemView* menu,
165                                    const ui::OSExchangeData& data) {
166   // Only accept drops of 1 node, which is the case for all data dragged from
167   // bookmark bar and menus.
168
169   if (!drop_data_.Read(data) || drop_data_.elements.size() != 1 ||
170       !profile_->GetPrefs()->GetBoolean(prefs::kEditBookmarksEnabled))
171     return false;
172
173   if (drop_data_.has_single_url())
174     return true;
175
176   const BookmarkNode* drag_node =
177       drop_data_.GetFirstNode(GetBookmarkModel(), profile_->GetPath());
178   if (!drag_node) {
179     // Dragging a folder from another profile, always accept.
180     return true;
181   }
182
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;
190   }
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);
196 }
197
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());
204
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;
214       }
215       index_to_drop_at++;
216       break;
217
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;
222       }
223       break;
224
225     case views::MenuDelegate::DROP_ON:
226       drop_parent = node;
227       index_to_drop_at = node->child_count();
228       break;
229
230     default:
231       break;
232   }
233   DCHECK(drop_parent);
234   return chrome::GetBookmarkDropOperation(
235       profile_, event, drop_data_, drop_parent, index_to_drop_at);
236 }
237
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;
253
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;
259
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();
266       }
267       break;
268
269     default:
270       break;
271   }
272
273   return chrome::DropBookmarks(profile_, drop_data_,
274                                drop_parent, index_to_drop_at);
275 }
276
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;
300 }
301
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();
306 }
307
308 void BookmarkMenuDelegate::WriteDragData(MenuItemView* sender,
309                                          ui::OSExchangeData* data) {
310   DCHECK(sender && data);
311
312   content::RecordAction(UserMetricsAction("BookmarkBar_DragFromFolder"));
313
314   BookmarkNodeData drag_data(menu_id_to_node_map_[sender->GetCommand()]);
315   drag_data.Write(profile_->GetPath(), data);
316 }
317
318 int BookmarkMenuDelegate::GetDragOperations(MenuItemView* sender) {
319   return chrome::GetBookmarkDragOperation(
320       profile_, menu_id_to_node_map_[sender->GetCommand()]);
321 }
322
323 int BookmarkMenuDelegate::GetMaxWidthForMenu(MenuItemView* menu) {
324   return kMaxMenuWidth;
325 }
326
327 void BookmarkMenuDelegate::BookmarkModelChanged() {
328 }
329
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.
336
337   menu_pair->second->SetIcon(model->GetFavicon(node).AsImageSkia());
338 }
339
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().
344
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);
348
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));
362       }
363       node_to_menu_map_.erase(node_to_menu);
364       menu_id_to_node_map_.erase(menu->GetCommand());
365     }
366   }
367
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);
373
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;
383       }
384     }
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;
390     }
391   }
392
393   for (std::set<MenuItemView*>::const_iterator i(changed_parent_menus.begin());
394        i != changed_parent_menus.end(); ++i)
395     (*i)->ChildrenChanged();
396 }
397
398 void BookmarkMenuDelegate::DidRemoveBookmarks() {
399   // Balances remove in WillRemoveBookmarksImpl.
400   GetBookmarkModel()->AddObserver(this);
401   DCHECK(is_mutating_model_);
402   is_mutating_model_ = false;
403 }
404
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;
416 }
417
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);
427 }
428
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.
436
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)++;
442
443   if (!*added_separator) {
444     *added_separator = true;
445     menu->AppendSeparator();
446   }
447
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;
454 }
455
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;
469
470     (*next_menu_id)++;
471
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();
487     }
488   }
489 }
490
491 bool BookmarkMenuDelegate::IsOutsideMenuIdRange(int menu_id) const {
492   return menu_id < min_menu_id_ || menu_id > max_menu_id_;
493 }