Upstream version 10.39.225.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/bookmarks/chrome_bookmark_client.h"
11 #include "chrome/browser/bookmarks/chrome_bookmark_client_factory.h"
12 #include "chrome/browser/profiles/profile.h"
13 #include "chrome/browser/ui/bookmarks/bookmark_drag_drop.h"
14 #include "chrome/browser/ui/bookmarks/bookmark_utils.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/bookmarks/bookmark_drag_drop_views.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"
32
33 using base::UserMetricsAction;
34 using bookmarks::BookmarkNodeData;
35 using content::PageNavigator;
36 using views::MenuItemView;
37
38 // Max width of a menu. There does not appear to be an OS value for this, yet
39 // both IE and FF restrict the max width of a menu.
40 static const int kMaxMenuWidth = 400;
41
42 BookmarkMenuDelegate::BookmarkMenuDelegate(Browser* browser,
43                                            PageNavigator* navigator,
44                                            views::Widget* parent,
45                                            int first_menu_id,
46                                            int max_menu_id)
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_(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) {}
59
60 BookmarkMenuDelegate::~BookmarkMenuDelegate() {
61   GetBookmarkModel()->RemoveObserver(this);
62 }
63
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
76     // Add a separator if there are existing items in the menu, and if the
77     // current node has children. If |node| is the bookmark bar then the
78     // managed node is shown as its first child, if it's not empty.
79     BookmarkModel* model = GetBookmarkModel();
80     ChromeBookmarkClient* client = GetChromeBookmarkClient();
81     bool show_managed = show_options == SHOW_PERMANENT_FOLDERS &&
82                         node == model->bookmark_bar_node() &&
83                         !client->managed_node()->empty();
84     bool has_children =
85         (start_child_index < node->child_count()) || show_managed;
86     int initial_count = parent->GetSubmenu() ?
87         parent->GetSubmenu()->GetMenuItemCount() : 0;
88     if (has_children && initial_count > 0)
89       parent->AppendSeparator();
90     if (show_managed)
91       BuildMenuForManagedNode(parent, &next_menu_id_);
92     BuildMenu(node, start_child_index, parent, &next_menu_id_);
93     if (show_options == SHOW_PERMANENT_FOLDERS)
94       BuildMenusForPermanentNodes(parent, &next_menu_id_);
95   } else {
96     menu_ = CreateMenu(node, start_child_index, show_options);
97   }
98 }
99
100 void BookmarkMenuDelegate::SetPageNavigator(PageNavigator* navigator) {
101   page_navigator_ = navigator;
102   if (context_menu_.get())
103     context_menu_->SetPageNavigator(navigator);
104 }
105
106 BookmarkModel* BookmarkMenuDelegate::GetBookmarkModel() {
107   return BookmarkModelFactory::GetForProfile(profile_);
108 }
109
110 ChromeBookmarkClient* BookmarkMenuDelegate::GetChromeBookmarkClient() {
111   return ChromeBookmarkClientFactory::GetForProfile(profile_);
112 }
113
114 void BookmarkMenuDelegate::SetActiveMenu(const BookmarkNode* node,
115                                          int start_index) {
116   DCHECK(!parent_menu_item_);
117   if (!node_to_menu_map_[node])
118     CreateMenu(node, start_index, HIDE_PERMANENT_FOLDERS);
119   menu_ = node_to_menu_map_[node];
120 }
121
122 base::string16 BookmarkMenuDelegate::GetTooltipText(
123     int id,
124     const gfx::Point& screen_loc) const {
125   MenuIDToNodeMap::const_iterator i = menu_id_to_node_map_.find(id);
126   // When removing bookmarks it may be possible to end up here without a node.
127   if (i == menu_id_to_node_map_.end()) {
128     DCHECK(is_mutating_model_);
129     return base::string16();
130   }
131
132   const BookmarkNode* node = i->second;
133   if (node->is_url()) {
134     return BookmarkBarView::CreateToolTipForURLAndTitle(
135         parent_, screen_loc, node->url(), node->GetTitle(), profile_);
136   }
137   return base::string16();
138 }
139
140 bool BookmarkMenuDelegate::IsTriggerableEvent(views::MenuItemView* menu,
141                                               const ui::Event& e) {
142   return e.type() == ui::ET_GESTURE_TAP ||
143          e.type() == ui::ET_GESTURE_TAP_DOWN ||
144          event_utils::IsPossibleDispositionEvent(e);
145 }
146
147 void BookmarkMenuDelegate::ExecuteCommand(int id, int mouse_event_flags) {
148   DCHECK(menu_id_to_node_map_.find(id) != menu_id_to_node_map_.end());
149
150   const BookmarkNode* node = menu_id_to_node_map_[id];
151   std::vector<const BookmarkNode*> selection;
152   selection.push_back(node);
153
154   chrome::OpenAll(parent_->GetNativeWindow(), page_navigator_, selection,
155                   ui::DispositionFromEventFlags(mouse_event_flags),
156                   profile_);
157   RecordBookmarkLaunch(node, location_);
158 }
159
160 bool BookmarkMenuDelegate::ShouldExecuteCommandWithoutClosingMenu(
161     int id,
162     const ui::Event& event) {
163   return (event.flags() & ui::EF_LEFT_MOUSE_BUTTON) &&
164          ui::DispositionFromEventFlags(event.flags()) == NEW_BACKGROUND_TAB;
165 }
166
167 bool BookmarkMenuDelegate::GetDropFormats(
168     MenuItemView* menu,
169     int* formats,
170     std::set<ui::OSExchangeData::CustomFormat>* custom_formats) {
171   *formats = ui::OSExchangeData::URL;
172   custom_formats->insert(BookmarkNodeData::GetBookmarkCustomFormat());
173   return true;
174 }
175
176 bool BookmarkMenuDelegate::AreDropTypesRequired(MenuItemView* menu) {
177   return true;
178 }
179
180 bool BookmarkMenuDelegate::CanDrop(MenuItemView* menu,
181                                    const ui::OSExchangeData& data) {
182   // Only accept drops of 1 node, which is the case for all data dragged from
183   // bookmark bar and menus.
184
185   if (!drop_data_.Read(data) || drop_data_.elements.size() != 1 ||
186       !profile_->GetPrefs()->GetBoolean(
187           bookmarks::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
215 int 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
255 int 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
295 bool 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
320 bool 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
326 void 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
336 int BookmarkMenuDelegate::GetDragOperations(MenuItemView* sender) {
337   return chrome::GetBookmarkDragOperation(
338       profile_, menu_id_to_node_map_[sender->GetCommand()]);
339 }
340
341 int BookmarkMenuDelegate::GetMaxWidthForMenu(MenuItemView* menu) {
342   return kMaxMenuWidth;
343 }
344
345 void BookmarkMenuDelegate::BookmarkModelChanged() {
346 }
347
348 void 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
358 void 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
416 void BookmarkMenuDelegate::DidRemoveBookmarks() {
417   // Balances remove in WillRemoveBookmarksImpl.
418   GetBookmarkModel()->AddObserver(this);
419   DCHECK(is_mutating_model_);
420   is_mutating_model_ = false;
421 }
422
423 MenuItemView* 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
439 void 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(), IDR_BOOKMARK_BAR_FOLDER, menu,
445                             next_menu_id, &added_separator);
446   BuildMenuForPermanentNode(model->mobile_node(), IDR_BOOKMARK_BAR_FOLDER, menu,
447                             next_menu_id, &added_separator);
448 }
449
450 void BookmarkMenuDelegate::BuildMenuForPermanentNode(
451     const BookmarkNode* node,
452     int icon_resource_id,
453     MenuItemView* menu,
454     int* next_menu_id,
455     bool* added_separator) {
456   if (!node->IsVisible() || node->GetTotalNodeCount() == 1)
457     return;  // No children, don't create a menu.
458
459   int id = *next_menu_id;
460   // Don't create the submenu if its menu ID will be outside the range allowed.
461   if (IsOutsideMenuIdRange(id))
462     return;
463   (*next_menu_id)++;
464
465   if (!*added_separator) {
466     *added_separator = true;
467     menu->AppendSeparator();
468   }
469
470   ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance();
471   gfx::ImageSkia* folder_icon = rb->GetImageSkiaNamed(icon_resource_id);
472   MenuItemView* submenu = menu->AppendSubMenuWithIcon(
473       id, node->GetTitle(), *folder_icon);
474   BuildMenu(node, 0, submenu, next_menu_id);
475   menu_id_to_node_map_[id] = node;
476 }
477
478 void BookmarkMenuDelegate::BuildMenuForManagedNode(
479     MenuItemView* menu,
480     int* next_menu_id) {
481   // Don't add a separator for this menu.
482   bool added_separator = true;
483   const BookmarkNode* node = GetChromeBookmarkClient()->managed_node();
484   BuildMenuForPermanentNode(node, IDR_BOOKMARK_BAR_FOLDER_MANAGED, menu,
485                             next_menu_id, &added_separator);
486 }
487
488 void 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
523 bool BookmarkMenuDelegate::IsOutsideMenuIdRange(int menu_id) const {
524   return menu_id < min_menu_id_ || menu_id > max_menu_id_;
525 }