• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2011 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_controller_views.h"
6 
7 #include "base/stl_util-inl.h"
8 #include "base/utf_string_conversions.h"
9 #include "chrome/browser/bookmarks/bookmark_model.h"
10 #include "chrome/browser/bookmarks/bookmark_node_data.h"
11 #include "chrome/browser/bookmarks/bookmark_utils.h"
12 #include "chrome/browser/metrics/user_metrics.h"
13 #include "chrome/browser/prefs/pref_service.h"
14 #include "chrome/browser/profiles/profile.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/event_utils.h"
18 #include "chrome/common/pref_names.h"
19 #include "content/browser/tab_contents/page_navigator.h"
20 #include "content/common/page_transition_types.h"
21 #include "grit/app_resources.h"
22 #include "grit/generated_resources.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 "views/controls/button/menu_button.h"
27 
28 using views::MenuItemView;
29 
30 // Max width of a menu. There does not appear to be an OS value for this, yet
31 // both IE and FF restrict the max width of a menu.
32 static const int kMaxMenuWidth = 400;
33 
BookmarkMenuController(Browser * browser,Profile * profile,PageNavigator * navigator,gfx::NativeWindow parent,const BookmarkNode * node,int start_child_index)34 BookmarkMenuController::BookmarkMenuController(Browser* browser,
35                                                Profile* profile,
36                                                PageNavigator* navigator,
37                                                gfx::NativeWindow parent,
38                                                const BookmarkNode* node,
39                                                int start_child_index)
40     : browser_(browser),
41       profile_(profile),
42       page_navigator_(navigator),
43       parent_(parent),
44       node_(node),
45       menu_(NULL),
46       observer_(NULL),
47       for_drop_(false),
48       bookmark_bar_(NULL),
49       next_menu_id_(1) {
50   menu_ = CreateMenu(node, start_child_index);
51 }
52 
RunMenuAt(BookmarkBarView * bookmark_bar,bool for_drop)53 void BookmarkMenuController::RunMenuAt(BookmarkBarView* bookmark_bar,
54                                        bool for_drop) {
55   bookmark_bar_ = bookmark_bar;
56   views::MenuButton* menu_button = bookmark_bar_->GetMenuButtonForNode(node_);
57   DCHECK(menu_button);
58   MenuItemView::AnchorPosition anchor;
59   int start_index;
60   bookmark_bar_->GetAnchorPositionAndStartIndexForButton(
61       menu_button, &anchor, &start_index);
62   RunMenuAt(menu_button, anchor, for_drop);
63 }
64 
RunMenuAt(views::MenuButton * button,MenuItemView::AnchorPosition position,bool for_drop)65 void BookmarkMenuController::RunMenuAt(
66     views::MenuButton* button,
67     MenuItemView::AnchorPosition position,
68     bool for_drop) {
69   gfx::Point screen_loc;
70   views::View::ConvertPointToScreen(button, &screen_loc);
71   // Subtract 1 from the height to make the popup flush with the button border.
72   gfx::Rect bounds(screen_loc.x(), screen_loc.y(), button->width(),
73                    button->height() - 1);
74   for_drop_ = for_drop;
75   profile_->GetBookmarkModel()->AddObserver(this);
76   // The constructor creates the initial menu and that is all we should have
77   // at this point.
78   DCHECK(node_to_menu_map_.size() == 1);
79   if (for_drop) {
80     menu_->RunMenuForDropAt(parent_, bounds, position);
81   } else {
82     menu_->RunMenuAt(parent_, button, bounds, position, false);
83     delete this;
84   }
85 }
86 
Cancel()87 void BookmarkMenuController::Cancel() {
88   menu_->Cancel();
89 }
90 
GetTooltipText(int id,const gfx::Point & screen_loc)91 std::wstring BookmarkMenuController::GetTooltipText(
92     int id, const gfx::Point& screen_loc) {
93   DCHECK(menu_id_to_node_map_.find(id) != menu_id_to_node_map_.end());
94 
95   const BookmarkNode* node = menu_id_to_node_map_[id];
96   if (node->type() == BookmarkNode::URL)
97     return BookmarkBarView::CreateToolTipForURLAndTitle(
98         screen_loc, node->GetURL(), UTF16ToWide(node->GetTitle()), profile_);
99   return std::wstring();
100 }
101 
IsTriggerableEvent(const views::MouseEvent & e)102 bool BookmarkMenuController::IsTriggerableEvent(const views::MouseEvent& e) {
103   return event_utils::IsPossibleDispositionEvent(e);
104 }
105 
ExecuteCommand(int id,int mouse_event_flags)106 void BookmarkMenuController::ExecuteCommand(int id, int mouse_event_flags) {
107   DCHECK(page_navigator_);
108   DCHECK(menu_id_to_node_map_.find(id) != menu_id_to_node_map_.end());
109 
110   const BookmarkNode* node = menu_id_to_node_map_[id];
111   std::vector<const BookmarkNode*> selection;
112   selection.push_back(node);
113 
114   WindowOpenDisposition initial_disposition =
115       event_utils::DispositionFromEventFlags(mouse_event_flags);
116 
117   bookmark_utils::OpenAll(parent_, profile_, page_navigator_, selection,
118                           initial_disposition);
119 }
120 
GetDropFormats(MenuItemView * menu,int * formats,std::set<ui::OSExchangeData::CustomFormat> * custom_formats)121 bool BookmarkMenuController::GetDropFormats(
122       MenuItemView* menu,
123       int* formats,
124       std::set<ui::OSExchangeData::CustomFormat>* custom_formats) {
125   *formats = ui::OSExchangeData::URL;
126   custom_formats->insert(BookmarkNodeData::GetBookmarkCustomFormat());
127   return true;
128 }
129 
AreDropTypesRequired(MenuItemView * menu)130 bool BookmarkMenuController::AreDropTypesRequired(MenuItemView* menu) {
131   return true;
132 }
133 
CanDrop(MenuItemView * menu,const ui::OSExchangeData & data)134 bool BookmarkMenuController::CanDrop(MenuItemView* menu,
135                                      const ui::OSExchangeData& data) {
136   // Only accept drops of 1 node, which is the case for all data dragged from
137   // bookmark bar and menus.
138 
139   if (!drop_data_.Read(data) || drop_data_.elements.size() != 1 ||
140       !profile_->GetPrefs()->GetBoolean(prefs::kEditBookmarksEnabled))
141     return false;
142 
143   if (drop_data_.has_single_url())
144     return true;
145 
146   const BookmarkNode* drag_node = drop_data_.GetFirstNode(profile_);
147   if (!drag_node) {
148     // Dragging a folder from another profile, always accept.
149     return true;
150   }
151 
152   // Drag originated from same profile and is not a URL. Only accept it if
153   // the dragged node is not a parent of the node menu represents.
154   const BookmarkNode* drop_node = menu_id_to_node_map_[menu->GetCommand()];
155   DCHECK(drop_node);
156   while (drop_node && drop_node != drag_node)
157     drop_node = drop_node->parent();
158   return (drop_node == NULL);
159 }
160 
GetDropOperation(MenuItemView * item,const views::DropTargetEvent & event,DropPosition * position)161 int BookmarkMenuController::GetDropOperation(
162     MenuItemView* item,
163     const views::DropTargetEvent& event,
164     DropPosition* position) {
165   // Should only get here if we have drop data.
166   DCHECK(drop_data_.is_valid());
167 
168   const BookmarkNode* node = menu_id_to_node_map_[item->GetCommand()];
169   const BookmarkNode* drop_parent = node->parent();
170   int index_to_drop_at = drop_parent->GetIndexOf(node);
171   if (*position == DROP_AFTER) {
172     index_to_drop_at++;
173   } else if (*position == DROP_ON) {
174     drop_parent = node;
175     index_to_drop_at = node->child_count();
176   }
177   DCHECK(drop_parent);
178   return bookmark_utils::BookmarkDropOperation(
179       profile_, event, drop_data_, drop_parent, index_to_drop_at);
180 }
181 
OnPerformDrop(MenuItemView * menu,DropPosition position,const views::DropTargetEvent & event)182 int BookmarkMenuController::OnPerformDrop(MenuItemView* menu,
183                                           DropPosition position,
184                                           const views::DropTargetEvent& event) {
185   const BookmarkNode* drop_node = menu_id_to_node_map_[menu->GetCommand()];
186   DCHECK(drop_node);
187   BookmarkModel* model = profile_->GetBookmarkModel();
188   DCHECK(model);
189   const BookmarkNode* drop_parent = drop_node->parent();
190   DCHECK(drop_parent);
191   int index_to_drop_at = drop_parent->GetIndexOf(drop_node);
192   if (position == DROP_AFTER) {
193     index_to_drop_at++;
194   } else if (position == DROP_ON) {
195     DCHECK(drop_node->is_folder());
196     drop_parent = drop_node;
197     index_to_drop_at = drop_node->child_count();
198   }
199 
200   int result = bookmark_utils::PerformBookmarkDrop(
201       profile_, drop_data_, drop_parent, index_to_drop_at);
202   if (for_drop_)
203     delete this;
204   return result;
205 }
206 
ShowContextMenu(MenuItemView * source,int id,const gfx::Point & p,bool is_mouse_gesture)207 bool BookmarkMenuController::ShowContextMenu(MenuItemView* source,
208                                              int id,
209                                              const gfx::Point& p,
210                                              bool is_mouse_gesture) {
211   DCHECK(menu_id_to_node_map_.find(id) != menu_id_to_node_map_.end());
212   std::vector<const BookmarkNode*> nodes;
213   nodes.push_back(menu_id_to_node_map_[id]);
214   context_menu_.reset(
215       new BookmarkContextMenu(
216           parent_,
217           profile_,
218           page_navigator_,
219           nodes[0]->parent(),
220           nodes));
221   context_menu_->set_observer(this);
222   context_menu_->RunMenuAt(p);
223   context_menu_.reset(NULL);
224   return true;
225 }
226 
DropMenuClosed(MenuItemView * menu)227 void BookmarkMenuController::DropMenuClosed(MenuItemView* menu) {
228   delete this;
229 }
230 
CanDrag(MenuItemView * menu)231 bool BookmarkMenuController::CanDrag(MenuItemView* menu) {
232   return true;
233 }
234 
WriteDragData(MenuItemView * sender,ui::OSExchangeData * data)235 void BookmarkMenuController::WriteDragData(MenuItemView* sender,
236                                            ui::OSExchangeData* data) {
237   DCHECK(sender && data);
238 
239   UserMetrics::RecordAction(UserMetricsAction("BookmarkBar_DragFromFolder"),
240                             profile_);
241 
242   BookmarkNodeData drag_data(menu_id_to_node_map_[sender->GetCommand()]);
243   drag_data.Write(profile_, data);
244 }
245 
GetDragOperations(MenuItemView * sender)246 int BookmarkMenuController::GetDragOperations(MenuItemView* sender) {
247   return bookmark_utils::BookmarkDragOperation(profile_,
248       menu_id_to_node_map_[sender->GetCommand()]);
249 }
250 
GetSiblingMenu(views::MenuItemView * menu,const gfx::Point & screen_point,views::MenuItemView::AnchorPosition * anchor,bool * has_mnemonics,views::MenuButton ** button)251 views::MenuItemView* BookmarkMenuController::GetSiblingMenu(
252     views::MenuItemView* menu,
253     const gfx::Point& screen_point,
254     views::MenuItemView::AnchorPosition* anchor,
255     bool* has_mnemonics,
256     views::MenuButton** button) {
257   if (!bookmark_bar_ || for_drop_)
258     return NULL;
259   gfx::Point bookmark_bar_loc(screen_point);
260   views::View::ConvertPointToView(NULL, bookmark_bar_, &bookmark_bar_loc);
261   int start_index;
262   const BookmarkNode* node =
263       bookmark_bar_->GetNodeForButtonAt(bookmark_bar_loc, &start_index);
264   if (!node || !node->is_folder())
265     return NULL;
266 
267   MenuItemView* alt_menu = node_to_menu_map_[node];
268   if (!alt_menu)
269     alt_menu = CreateMenu(node, start_index);
270 
271   menu_ = alt_menu;
272 
273   *button = bookmark_bar_->GetMenuButtonForNode(node);
274   bookmark_bar_->GetAnchorPositionAndStartIndexForButton(
275       *button, anchor, &start_index);
276   *has_mnemonics = false;
277   return alt_menu;
278 }
279 
GetMaxWidthForMenu()280 int BookmarkMenuController::GetMaxWidthForMenu() {
281   return kMaxMenuWidth;
282 }
283 
BookmarkModelChanged()284 void BookmarkMenuController::BookmarkModelChanged() {
285   menu_->Cancel();
286 }
287 
BookmarkNodeFaviconLoaded(BookmarkModel * model,const BookmarkNode * node)288 void BookmarkMenuController::BookmarkNodeFaviconLoaded(
289     BookmarkModel* model, const BookmarkNode* node) {
290   NodeToMenuIDMap::iterator menu_pair = node_to_menu_id_map_.find(node);
291   if (menu_pair == node_to_menu_id_map_.end())
292     return;  // We're not showing a menu item for the node.
293 
294   // Iterate through the menus looking for the menu containing node.
295   for (NodeToMenuMap::iterator i = node_to_menu_map_.begin();
296        i != node_to_menu_map_.end(); ++i) {
297     MenuItemView* menu_item = i->second->GetMenuItemByID(menu_pair->second);
298     if (menu_item) {
299       menu_item->SetIcon(model->GetFavicon(node));
300       return;
301     }
302   }
303 }
304 
WillRemoveBookmarks(const std::vector<const BookmarkNode * > & bookmarks)305 void BookmarkMenuController::WillRemoveBookmarks(
306     const std::vector<const BookmarkNode*>& bookmarks) {
307   std::set<MenuItemView*> removed_menus;
308 
309   WillRemoveBookmarksImpl(bookmarks, &removed_menus);
310 
311   STLDeleteElements(&removed_menus);
312 }
313 
DidRemoveBookmarks()314 void BookmarkMenuController::DidRemoveBookmarks() {
315   profile_->GetBookmarkModel()->AddObserver(this);
316 }
317 
CreateMenu(const BookmarkNode * parent,int start_child_index)318 MenuItemView* BookmarkMenuController::CreateMenu(const BookmarkNode* parent,
319                                                  int start_child_index) {
320   MenuItemView* menu = new MenuItemView(this);
321   menu->SetCommand(next_menu_id_++);
322   menu_id_to_node_map_[menu->GetCommand()] = parent;
323   menu->set_has_icons(true);
324   BuildMenu(parent, start_child_index, menu, &next_menu_id_);
325   node_to_menu_map_[parent] = menu;
326   return menu;
327 }
328 
BuildMenu(const BookmarkNode * parent,int start_child_index,MenuItemView * menu,int * next_menu_id)329 void BookmarkMenuController::BuildMenu(const BookmarkNode* parent,
330                                        int start_child_index,
331                                        MenuItemView* menu,
332                                        int* next_menu_id) {
333   DCHECK(!parent->child_count() ||
334          start_child_index < parent->child_count());
335   for (int i = start_child_index; i < parent->child_count(); ++i) {
336     const BookmarkNode* node = parent->GetChild(i);
337     int id = *next_menu_id;
338 
339     (*next_menu_id)++;
340     if (node->is_url()) {
341       SkBitmap icon = profile_->GetBookmarkModel()->GetFavicon(node);
342       if (icon.width() == 0) {
343         icon = *ResourceBundle::GetSharedInstance().
344             GetBitmapNamed(IDR_DEFAULT_FAVICON);
345       }
346       menu->AppendMenuItemWithIcon(id, UTF16ToWide(node->GetTitle()), icon);
347       node_to_menu_id_map_[node] = id;
348     } else if (node->is_folder()) {
349       SkBitmap* folder_icon = ResourceBundle::GetSharedInstance().
350           GetBitmapNamed(IDR_BOOKMARK_BAR_FOLDER);
351       MenuItemView* submenu = menu->AppendSubMenuWithIcon(id,
352           UTF16ToWide(node->GetTitle()), *folder_icon);
353       node_to_menu_id_map_[node] = id;
354       BuildMenu(node, 0, submenu, next_menu_id);
355     } else {
356       NOTREACHED();
357     }
358     menu_id_to_node_map_[id] = node;
359   }
360 }
361 
~BookmarkMenuController()362 BookmarkMenuController::~BookmarkMenuController() {
363   profile_->GetBookmarkModel()->RemoveObserver(this);
364   if (observer_)
365     observer_->BookmarkMenuDeleted(this);
366   STLDeleteValues(&node_to_menu_map_);
367 }
368 
GetMenuByID(int id)369 MenuItemView* BookmarkMenuController::GetMenuByID(int id) {
370   for (NodeToMenuMap::const_iterator i = node_to_menu_map_.begin();
371        i != node_to_menu_map_.end(); ++i) {
372     MenuItemView* menu = i->second->GetMenuItemByID(id);
373     if (menu)
374       return menu;
375   }
376   return NULL;
377 }
378 
WillRemoveBookmarksImpl(const std::vector<const BookmarkNode * > & bookmarks,std::set<views::MenuItemView * > * removed_menus)379 void BookmarkMenuController::WillRemoveBookmarksImpl(
380       const std::vector<const BookmarkNode*>& bookmarks,
381       std::set<views::MenuItemView*>* removed_menus) {
382   // Remove the observer so that when the remove happens we don't prematurely
383   // cancel the menu.
384   profile_->GetBookmarkModel()->RemoveObserver(this);
385 
386   // Remove the menu items.
387   std::set<MenuItemView*> changed_parent_menus;
388   for (std::vector<const BookmarkNode*>::const_iterator i = bookmarks.begin();
389        i != bookmarks.end(); ++i) {
390     NodeToMenuIDMap::iterator node_to_menu = node_to_menu_id_map_.find(*i);
391     if (node_to_menu != node_to_menu_id_map_.end()) {
392       MenuItemView* menu = GetMenuByID(node_to_menu->second);
393       DCHECK(menu);  // If there an entry in node_to_menu_id_map_, there should
394                      // be a menu.
395       removed_menus->insert(menu);
396       changed_parent_menus.insert(menu->GetParentMenuItem());
397       menu->parent()->RemoveChildView(menu);
398       node_to_menu_id_map_.erase(node_to_menu);
399     }
400   }
401 
402   // All the bookmarks in |bookmarks| should have the same parent. It's possible
403   // to support different parents, but this would need to prune any nodes whose
404   // parent has been removed. As all nodes currently have the same parent, there
405   // is the DCHECK.
406   DCHECK(changed_parent_menus.size() <= 1);
407 
408   for (std::set<MenuItemView*>::const_iterator i = changed_parent_menus.begin();
409        i != changed_parent_menus.end(); ++i) {
410     (*i)->ChildrenChanged();
411   }
412 
413   // Remove any descendants of the removed nodes in node_to_menu_id_map_.
414   for (NodeToMenuIDMap::iterator i = node_to_menu_id_map_.begin();
415        i != node_to_menu_id_map_.end(); ) {
416     bool ancestor_removed = false;
417     for (std::vector<const BookmarkNode*>::const_iterator j = bookmarks.begin();
418          j != bookmarks.end(); ++j) {
419       if (i->first->HasAncestor(*j)) {
420         ancestor_removed = true;
421         break;
422       }
423     }
424     if (ancestor_removed) {
425       node_to_menu_id_map_.erase(i++);
426     } else {
427       ++i;
428     }
429   }
430 }
431